├── rustfmt.toml ├── .node-version ├── packages ├── netgrep │ ├── .gitignore │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── data │ │ │ ├── NetgrepConfig.ts │ │ │ ├── BatchNetgrepResult.ts │ │ │ ├── NetgrepSearchConfig.ts │ │ │ ├── NetgrepInput.ts │ │ │ └── NetgrepResult.ts │ │ │ ├── Netgrep.spec.ts │ │ │ └── Netgrep.ts │ ├── README.md │ ├── package.json │ ├── tsconfig.spec.json │ ├── tsconfig.lib.json │ ├── yarn.lock │ ├── .eslintrc.json │ ├── jest.config.js │ ├── tsconfig.json │ └── project.json ├── search │ ├── .gitignore │ ├── README.md │ ├── scripts │ │ └── post_build.js │ ├── Cargo.toml │ ├── tests │ │ └── search.rs │ ├── project.json │ └── src │ │ └── lib.rs └── example │ ├── .gitignore │ ├── project.json │ ├── webpack.config.js │ ├── README.md │ ├── src │ ├── index.html │ ├── index.js │ └── inputs.js │ └── assets │ ├── veil.txt │ ├── reti.txt │ ├── dyin.txt │ └── maza.txt ├── babel.config.json ├── rust-toolchain.toml ├── Cargo.toml ├── .prettierrc ├── assets └── header.jpg ├── .prettierignore ├── jest.preset.js ├── jest.config.ts ├── workspace.json ├── .editorconfig ├── tsconfig.base.json ├── .gitignore ├── .github └── workflows │ ├── publish-netgrep.yml │ ├── test-and-lint.yml │ └── publish-search.yml ├── .eslintrc.json ├── LICENSE ├── nx.json ├── package.json ├── README.md └── Cargo.lock /rustfmt.toml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | v18.7.0 -------------------------------------------------------------------------------- /packages/netgrep/.gitignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /packages/search/.gitignore: -------------------------------------------------------------------------------- 1 | pkg -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "babelrcRoots": ["*"] 3 | } 4 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | -------------------------------------------------------------------------------- /packages/netgrep/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/Netgrep.js'; 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "packages/search" 4 | ] 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "jsxSingleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /assets/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgopsq/netgrep/HEAD/assets/header.jpg -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nrwl/jest/preset').default; 2 | 3 | module.exports = { ...nxPreset }; 4 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import { getJestProjects } from '@nrwl/jest'; 2 | 3 | export default { 4 | projects: getJestProjects(), 5 | }; 6 | -------------------------------------------------------------------------------- /packages/netgrep/README.md: -------------------------------------------------------------------------------- 1 | # @netgrep/netgrep 2 | 3 | The main `netgrep` package. See the [main README](https://github.com/dgopsq/netgrep) for more informations. -------------------------------------------------------------------------------- /packages/netgrep/src/lib/data/NetgrepConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The global Netgrep configuration object. 3 | */ 4 | export type NetgrepConfig = { 5 | enableMemoryCache: boolean; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/netgrep/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@netgrep/netgrep", 3 | "version": "0.1.5", 4 | "type": "module", 5 | "dependencies": { 6 | "@netgrep/search": "^0.1.5" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/search/README.md: -------------------------------------------------------------------------------- 1 | # @netgrep/search 2 | 3 | The WASM porting of [ripgrep](https://github.com/BurntSushi/ripgrep). See the [main README](https://github.com/dgopsq/netgrep) for more informations. -------------------------------------------------------------------------------- /workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/workspace-schema.json", 3 | "version": 2, 4 | "projects": { 5 | "search": "packages/search", 6 | "example": "packages/example", 7 | "netgrep": "packages/netgrep" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/netgrep/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "types": ["jest", "node"] 6 | }, 7 | "include": ["jest.config.js", "./src/**/*.test.ts", "./src/**/*.spec.ts", "./src/**/*.d.ts"], 8 | } 9 | -------------------------------------------------------------------------------- /packages/netgrep/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "declaration": true, 6 | "types": [] 7 | }, 8 | "include": ["./src/**/*.ts"], 9 | "exclude": ["jest.config.js", "**/*.spec.ts", "**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /packages/netgrep/src/lib/data/BatchNetgrepResult.ts: -------------------------------------------------------------------------------- 1 | import { NetgrepResult } from './NetgrepResult.js'; 2 | 3 | /** 4 | * Type representing a `NetgrepResult` for a batch 5 | * search. 6 | */ 7 | export type BatchNetgrepResult = NetgrepResult & { 8 | error: string | null; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/netgrep/src/lib/data/NetgrepSearchConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The optional configuration passed in 3 | * a single search method. 4 | */ 5 | export type NetgrepSearchConfig = { 6 | /** 7 | * A `Signal` used to abort the remote file 8 | * search and download. 9 | */ 10 | signal?: AbortSignal; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/netgrep/src/lib/data/NetgrepInput.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type representing a single search target in a 3 | * batch search. The `T` generic is for the metadata 4 | * that will be returned back in the result object. 5 | */ 6 | export type NetgrepInput = { 7 | url: string; 8 | metadata?: T; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/netgrep/src/lib/data/NetgrepResult.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A result object returned for a search. The `T` generic 3 | * represents the metadata passed in the search method 4 | * used. 5 | */ 6 | export type NetgrepResult = { 7 | url: string; 8 | pattern: string; 9 | result: boolean; 10 | metadata?: T; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/example/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /packages/netgrep/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@netgrep/search@^0.1.5": 6 | version "0.1.5" 7 | resolved "https://registry.yarnpkg.com/@netgrep/search/-/search-0.1.5.tgz#66807a971c24ea59685bf48ed6b4f71c2b952d10" 8 | integrity sha512-EGBRjKC5I9+QDPMsYx/ghy4kDBp2/QOHa5KHn6dj1NMbNfwcW1RnTuIJ0iFa8qTOgcCDJSi2ktPspukHpUIO/w== 9 | -------------------------------------------------------------------------------- /packages/netgrep/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/example/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 3 | "sourceRoot": "packages/example/src", 4 | "projectType": "application", 5 | "targets": { 6 | "serve": { 7 | "executor": "nx:run-commands", 8 | "options": { 9 | "command": "webpack serve --open", 10 | "cwd": "packages/example" 11 | } 12 | } 13 | }, 14 | "tags": [] 15 | } 16 | -------------------------------------------------------------------------------- /packages/netgrep/jest.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'netgrep', 4 | preset: '../../jest.preset.js', 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | useESM: true, 9 | }, 10 | }, 11 | transform: { 12 | '^.+\\.[tj]s$': 'ts-jest', 13 | }, 14 | moduleFileExtensions: ['ts', 'js', 'html'], 15 | coverageDirectory: '../../coverage/packages/netgrep', 16 | }; 17 | -------------------------------------------------------------------------------- /packages/search/scripts/post_build.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const packageJsonPath = './pkg/package.json'; 4 | const rawPackage = fs.readFileSync(packageJsonPath); 5 | const parsedPackage = JSON.parse(rawPackage); 6 | 7 | // Add the `"type": "module"` to the package.json 8 | // in order to make it fully compatible as an ESM. 9 | parsedPackage.type = 'module'; 10 | 11 | const serializedPackage = JSON.stringify(parsedPackage, null, 2); 12 | 13 | fs.writeFileSync(packageJsonPath, serializedPackage); 14 | -------------------------------------------------------------------------------- /packages/example/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | mode: 'development', 7 | output: { 8 | filename: 'main.js', 9 | path: path.resolve(__dirname, 'dist'), 10 | }, 11 | experiments: { 12 | asyncWebAssembly: true, 13 | }, 14 | devServer: { 15 | static: ['assets', 'dist'], 16 | http2: true, 17 | }, 18 | plugins: [ 19 | new HtmlWebpackPlugin({ 20 | template: './src/index.html', 21 | }), 22 | ], 23 | }; 24 | -------------------------------------------------------------------------------- /packages/netgrep/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "ES2020", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.lib.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/example/README.md: -------------------------------------------------------------------------------- 1 | # Netgrep example 2 | 3 | This is an example for a basic usage of the `@netgrep/netgrep` package using [Webpack](https://webpack.js.org/). See the [main README](https://github.com/dgopsq/netgrep) for more informations. 4 | 5 | ## Usage 6 | 7 | From the root of this repository install all the dependencies: 8 | 9 | ```bash 10 | # Using yarn 11 | yarn add @netgrep/netgrep 12 | 13 | # Using npm 14 | npm install @netgrep/netgrep 15 | ``` 16 | 17 | and then run: 18 | 19 | ```bash 20 | # Using yarn 21 | yarn nx serve example 22 | 23 | # Using npm 24 | npx nx serve example 25 | ``` -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2017", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "@netgrep/netgrep": ["packages/netgrep/src/index.ts"] 19 | } 20 | }, 21 | "exclude": ["node_modules", "tmp"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/search/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "search" 3 | version = "0.1.5" 4 | edition = "2021" 5 | repository = "https://github.com/dgopsq/netgrep" 6 | description = "The power of ripgrep over HTTP" 7 | license = "MIT" 8 | 9 | [lib] 10 | crate-type = ["cdylib", "rlib"] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | wasm-bindgen = "0.2.82" 16 | wee_alloc = "0.4.5" 17 | grep = { git = "https://github.com/dgopsq/ripgrep", tag = "13.0.0-wasm" } 18 | 19 | [dev-dependencies] 20 | wasm-bindgen-test = "0.3.32" 21 | 22 | [profile.release] 23 | lto = true 24 | opt-level = 's' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | /target -------------------------------------------------------------------------------- /.github/workflows/publish-netgrep.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - netgrep-** 5 | 6 | jobs: 7 | test-and-lint: 8 | uses: ./.github/workflows/test-and-lint.yml 9 | 10 | publish: 11 | needs: test-and-lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: '18.7.0' 18 | cache: 'yarn' 19 | - run: yarn 20 | - run: yarn nx build netgrep 21 | - uses: JS-DevTools/npm-publish@v1 22 | with: 23 | token: ${{ secrets.NPM_TOKEN }} 24 | access: public 25 | greater-version-only: true 26 | package: ./packages/netgrep/dist/package.json 27 | -------------------------------------------------------------------------------- /.github/workflows/test-and-lint.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | branches: 7 | - main 8 | workflow_call: 9 | 10 | name: Test and lint workflow 11 | 12 | jobs: 13 | test_and_lint: 14 | name: Test and lint 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions-rs/toolchain@v1 19 | with: 20 | toolchain: stable 21 | - uses: actions/setup-node@v3 22 | with: 23 | node-version: '18.7.0' 24 | cache: 'yarn' 25 | - run: yarn 26 | - run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 27 | - run: yarn nx run-many --target=lint 28 | - run: yarn nx run-many --target=test 29 | -------------------------------------------------------------------------------- /.github/workflows/publish-search.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - search-** 5 | 6 | jobs: 7 | test-and-lint: 8 | uses: ./.github/workflows/test-and-lint.yml 9 | 10 | publish: 11 | needs: test-and-lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | toolchain: stable 18 | - uses: actions/setup-node@v3 19 | with: 20 | node-version: '18.7.0' 21 | cache: 'yarn' 22 | - run: yarn 23 | - run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 24 | - run: yarn nx build search 25 | - uses: JS-DevTools/npm-publish@v1 26 | with: 27 | token: ${{ secrets.NPM_TOKEN }} 28 | access: public 29 | greater-version-only: true 30 | package: ./packages/search/pkg/package.json 31 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nrwl/nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nrwl/nx/enforce-module-boundaries": [ 10 | "error", 11 | { 12 | "enforceBuildableLibDependency": true, 13 | "allow": [], 14 | "depConstraints": [ 15 | { 16 | "sourceTag": "*", 17 | "onlyDependOnLibsWithTags": ["*"] 18 | } 19 | ] 20 | } 21 | ], 22 | "no-console": "warn" 23 | } 24 | }, 25 | { 26 | "files": ["*.ts", "*.tsx"], 27 | "extends": ["plugin:@nrwl/nx/typescript"], 28 | "rules": {} 29 | }, 30 | { 31 | "files": ["*.js", "*.jsx"], 32 | "extends": ["plugin:@nrwl/nx/javascript"], 33 | "rules": {} 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/example/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 🦄 Netgrep Example ✨ 9 | 10 | 11 | 12 |
13 |
14 |

🦄 Netgrep Example ✨

15 |
16 | 17 |
18 | Type "sherlock" here: 19 | 20 |
21 | 28 |
29 |
30 | 31 |
32 |
    33 |
    34 |
    35 | 36 | 37 | -------------------------------------------------------------------------------- /packages/netgrep/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 3 | "sourceRoot": "packages/netgrep/src", 4 | "projectType": "library", 5 | "targets": { 6 | "build": { 7 | "executor": "@nrwl/js:tsc", 8 | "outputs": ["{options.outputPath}"], 9 | "options": { 10 | "outputPath": "packages/netgrep/dist", 11 | "main": "packages/netgrep/src/index.ts", 12 | "tsConfig": "packages/netgrep/tsconfig.lib.json", 13 | "assets": ["README.md"] 14 | } 15 | }, 16 | "lint": { 17 | "executor": "@nrwl/linter:eslint", 18 | "outputs": ["{options.outputFile}"], 19 | "options": { 20 | "lintFilePatterns": ["packages/netgrep/**/*.ts"] 21 | } 22 | }, 23 | "test": { 24 | "executor": "@nrwl/jest:jest", 25 | "outputs": ["coverage/packages/netgrep"], 26 | "options": { 27 | "jestConfig": "packages/netgrep/jest.config.js", 28 | "passWithNoTests": true 29 | } 30 | } 31 | }, 32 | "tags": [] 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Diego Pasquali 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/search/tests/search.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod search { 3 | use wasm_bindgen_test::*; 4 | 5 | wasm_bindgen_test_configure!(run_in_browser); 6 | 7 | #[wasm_bindgen_test] 8 | pub fn test_search_bytes() { 9 | let text = "One Wiseman came to Jhaampe-town. \ 10 | He set aside both Queen and Crown \ 11 | Did his task and fell asleep \ 12 | Gave his bones to the stones to keep."; 13 | 14 | let bytes = text.as_bytes(); 15 | 16 | let result = search::search_bytes(bytes, "set aside"); 17 | 18 | assert_eq!(result, true); 19 | } 20 | 21 | #[wasm_bindgen_test] 22 | pub fn test_search_bytes_smart_case() { 23 | let text = "One Wiseman came to Jhaampe-town. \ 24 | He set aside both Queen and Crown \ 25 | Did his task and fell asleep \ 26 | Gave his bones to the stones to keep."; 27 | 28 | let bytes = text.as_bytes(); 29 | 30 | let result = search::search_bytes(bytes, "both queen and crown"); 31 | 32 | assert_eq!(result, true); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "npmScope": "netgrep", 4 | "affected": { 5 | "defaultBase": "main" 6 | }, 7 | "implicitDependencies": { 8 | "package.json": { 9 | "dependencies": "*", 10 | "devDependencies": "*" 11 | }, 12 | ".eslintrc.json": "*" 13 | }, 14 | "tasksRunnerOptions": { 15 | "default": { 16 | "runner": "nx/tasks-runners/default", 17 | "options": { 18 | "cacheableOperations": ["build", "lint", "test", "e2e"] 19 | } 20 | } 21 | }, 22 | "targetDefaults": { 23 | "build": { 24 | "dependsOn": ["^build"] 25 | } 26 | }, 27 | "workspaceLayout": { 28 | "appsDir": "packages", 29 | "libsDir": "packages" 30 | }, 31 | "plugins": ["@nxrs/cargo"], 32 | "defaultProject": "example", 33 | "generators": { 34 | "@nrwl/web:application": { 35 | "style": "css", 36 | "linter": "eslint", 37 | "unitTestRunner": "jest", 38 | "e2eTestRunner": "none" 39 | }, 40 | "@nrwl/web:library": { 41 | "style": "css", 42 | "linter": "eslint", 43 | "unitTestRunner": "jest" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/search/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 3 | "projectType": "library", 4 | "sourceRoot": "packages/search/src", 5 | "targets": { 6 | "build": { 7 | "executor": "nx:run-commands", 8 | "options": { 9 | "command": "wasm-pack build --scope netgrep --out-name index --release && node scripts/post_build.js", 10 | "cwd": "packages/search", 11 | "outputPath": "packages/search/pkg" 12 | } 13 | }, 14 | "publish": { 15 | "executor": "nx:run-commands", 16 | "options": { 17 | "command": "wasm-pack pack && wasm-pack publish", 18 | "cwd": "packages/search", 19 | "outputPath": "packages/search/packed" 20 | } 21 | }, 22 | "test": { 23 | "executor": "nx:run-commands", 24 | "options": { 25 | "command": "wasm-pack test --chrome --headless", 26 | "cwd": "packages/search" 27 | } 28 | }, 29 | "lint": { 30 | "executor": "@nxrs/cargo:clippy", 31 | "options": { 32 | "fix": false, 33 | "failOnWarnings": true, 34 | "noDeps": true 35 | } 36 | } 37 | }, 38 | "tags": [] 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@netgrep/main", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": {}, 6 | "private": true, 7 | "dependencies": { 8 | "@netgrep/netgrep": "^0.1.3", 9 | "core-js": "^3.6.5", 10 | "lodash": "^4.17.21", 11 | "regenerator-runtime": "0.13.7", 12 | "tslib": "^2.4.0" 13 | }, 14 | "devDependencies": { 15 | "@nrwl/cli": "14.5.4", 16 | "@nrwl/eslint-plugin-nx": "14.5.4", 17 | "@nrwl/jest": "^14.5.10", 18 | "@nrwl/js": "14.5.4", 19 | "@nrwl/linter": "14.5.4", 20 | "@nrwl/web": "^14.5.4", 21 | "@nrwl/workspace": "14.5.4", 22 | "@nxrs/cargo": "^0.3.3", 23 | "@types/jest": "^28.1.6", 24 | "@types/node": "^18.7.2", 25 | "@typescript-eslint/eslint-plugin": "^5.29.0", 26 | "@typescript-eslint/parser": "^5.29.0", 27 | "babel-jest": "^28.1.3", 28 | "eslint": "~8.15.0", 29 | "eslint-config-prettier": "8.1.0", 30 | "html-webpack-plugin": "^5.5.0", 31 | "jest": "^29.0.0", 32 | "jest-environment-jsdom": "^28.1.3", 33 | "nx": "14.5.4", 34 | "prettier": "^2.6.2", 35 | "ts-jest": "^28.0.7", 36 | "ts-node": "^10.9.1", 37 | "typescript": "~4.7.2", 38 | "webpack": "^5.74.0", 39 | "webpack-cli": "^4.10.0", 40 | "webpack-dev-server": "^4.10.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/search/src/lib.rs: -------------------------------------------------------------------------------- 1 | use grep::regex::RegexMatcherBuilder; 2 | use grep::searcher::{BinaryDetection, Searcher, SearcherBuilder, Sink, SinkMatch}; 3 | use wasm_bindgen::prelude::*; 4 | 5 | /// Use `wee_alloc` as the global allocator. 6 | #[global_allocator] 7 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 8 | 9 | /// Search a bytes array for the given pattern. This function 10 | /// uses `ripgrep` under the hood. 11 | #[wasm_bindgen] 12 | pub fn search_bytes(chunk: &[u8], pattern: &str) -> bool { 13 | let matcher = RegexMatcherBuilder::new() 14 | .line_terminator(Some(b'\n')) 15 | .case_smart(true) 16 | .build(pattern) 17 | .unwrap(); 18 | 19 | let mut searcher = SearcherBuilder::new() 20 | .binary_detection(BinaryDetection::quit(b'\x00')) 21 | .line_number(false) 22 | .build(); 23 | 24 | let mut sink = MemSink { match_count: 0 }; 25 | 26 | let _ = searcher.search_slice(&matcher, chunk, &mut sink); 27 | 28 | sink.match_count > 0 29 | } 30 | 31 | /// An in-memory `Sink` implementation in order 32 | /// to store the matches in a structured way instead 33 | /// of just writing on a stdout. 34 | struct MemSink { 35 | match_count: u64, 36 | } 37 | 38 | impl Sink for MemSink { 39 | type Error = std::io::Error; 40 | 41 | fn matched( 42 | &mut self, 43 | _searcher: &Searcher, 44 | _mat: &SinkMatch<'_>, 45 | ) -> Result { 46 | self.match_count += 1; 47 | Ok(true) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/example/src/index.js: -------------------------------------------------------------------------------- 1 | import { Netgrep } from '@netgrep/netgrep'; 2 | import debounce from 'lodash/debounce'; 3 | import { urls } from './inputs'; 4 | 5 | const NG = new Netgrep(); 6 | const searchInput = document.querySelector('#search-input'); 7 | const searchResultList = document.querySelector('#search-result'); 8 | 9 | /** 10 | * Execute a search manipulating the DOM in order 11 | * to show a list of urls where there is a match with 12 | * the given pattern. 13 | */ 14 | function execSearch(pattern) { 15 | // If the given pattern is falsy we are 16 | // going to reset the results' list. 17 | if (!pattern) { 18 | searchResultList.replaceChildren(); 19 | return; 20 | } 21 | 22 | NG.searchBatch(urls, pattern) 23 | .then((outputs) => { 24 | // Clear the results list before adding 25 | // the new items. 26 | searchResultList.replaceChildren(); 27 | 28 | const frag = document.createDocumentFragment(); 29 | 30 | // Iterate over all the results showing only 31 | // the ones with a positive `output.result`. 32 | outputs.forEach((output) => { 33 | if (!output.result) return; 34 | 35 | const resultEl = document.createElement('li'); 36 | resultEl.textContent = `✅ ${output.url}`; 37 | 38 | frag.appendChild(resultEl); 39 | }); 40 | 41 | searchResultList.appendChild(frag); 42 | }) 43 | .catch(console.error); 44 | } 45 | 46 | // Bind the `execSearch` function to the search field's 47 | // `input` event using the lodash `debounce` utility. 48 | searchInput.addEventListener( 49 | 'input', 50 | debounce((e) => execSearch(e.target.value), 250) 51 | ); 52 | -------------------------------------------------------------------------------- /packages/example/src/inputs.js: -------------------------------------------------------------------------------- 1 | export const urls = [ 2 | { url: '/3gab.txt' }, 3 | { url: '/3gar.txt' }, 4 | { url: '/3stu.txt' }, 5 | { url: '/abbe.txt' }, 6 | { url: '/advs.txt' }, 7 | { url: '/bery.txt' }, 8 | { url: '/blac.txt' }, 9 | { url: '/blan.txt' }, 10 | { url: '/blue.txt' }, 11 | { url: '/bosc.txt' }, 12 | { url: '/bruc.txt' }, 13 | { url: '/cano.txt' }, 14 | { url: '/card.txt' }, 15 | { url: '/case.txt' }, 16 | { url: '/chas.txt' }, 17 | { url: '/cnus.txt' }, 18 | { url: '/copp.txt' }, 19 | { url: '/cree.txt' }, 20 | { url: '/croo.txt' }, 21 | { url: '/danc.txt' }, 22 | { url: '/devi.txt' }, 23 | { url: '/dyin.txt' }, 24 | { url: '/empt.txt' }, 25 | { url: '/engr.txt' }, 26 | { url: '/fina.txt' }, 27 | { url: '/five.txt' }, 28 | { url: '/glor.txt' }, 29 | { url: '/gold.txt' }, 30 | { url: '/gree.txt' }, 31 | { url: '/houn.txt' }, 32 | { url: '/iden.txt' }, 33 | { url: '/illu.txt' }, 34 | { url: '/lady.txt' }, 35 | { url: '/last.txt' }, 36 | { url: '/lion.txt' }, 37 | { url: '/lstb.txt' }, 38 | { url: '/maza.txt' }, 39 | { url: '/mems.txt' }, 40 | { url: '/miss.txt' }, 41 | { url: '/musg.txt' }, 42 | { url: '/nava.txt' }, 43 | { url: '/nobl.txt' }, 44 | { url: '/norw.txt' }, 45 | { url: '/prio.txt' }, 46 | { url: '/redc.txt' }, 47 | { url: '/redh.txt' }, 48 | { url: '/reig.txt' }, 49 | { url: '/resi.txt' }, 50 | { url: '/reti.txt' }, 51 | { url: '/retn.txt' }, 52 | { url: '/scan.txt' }, 53 | { url: '/seco.txt' }, 54 | { url: '/shos.txt' }, 55 | { url: '/sign.txt' }, 56 | { url: '/silv.txt' }, 57 | { url: '/sixn.txt' }, 58 | { url: '/soli.txt' }, 59 | { url: '/spec.txt' }, 60 | { url: '/stoc.txt' }, 61 | { url: '/stud.txt' }, 62 | { url: '/suss.txt' }, 63 | { url: '/thor.txt' }, 64 | { url: '/twis.txt' }, 65 | { url: '/vall.txt' }, 66 | { url: '/veil.txt' }, 67 | { url: '/wist.txt' }, 68 | { url: '/yell.txt' }, 69 | ]; 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ![Header](https://github.com/dgopsq/netgrep/blob/main/assets/header.jpg) 4 | 5 | # netgrep 6 | 7 | Netgrep is an **experimental** porting of [ripgrep](https://github.com/BurntSushi/ripgrep) on WASM using the HTTP protocol. The scope of this project is to provide a viable alternative to index-based search engines for applications with a small files-based database. It uses [a fork of the original `ripgrep` repository](https://github.com/dgopsq/ripgrep) with just enough changes to make it runnable on WASM. 8 | 9 | At the moment Netgrep is just going to tell whether a pattern is present on a remote file leveraging the `ripgrep` core search engine. This happens **while the file is being downloaded** in order to maximize the performance. 10 | 11 | > **Note** 12 | > Searching for posts on a blog created through a Static Site Generator is an interesting use-case for this experiment. Netgrep could easily be used to create a real-time search engine from the raw post files. A live example for this behavior can be found on [my blog](https://diegopasquali.com/search) (you can take a look at the [source code](https://github.com/dgopsq/writings)). 13 | 14 | > **Warning** 15 | > At the moment this library is exported only as an ESM, thus a bundler like [Webpack](https://webpack.js.org/) is required to use it. 16 | 17 | ## Usage 18 | 19 | > **Note** 20 | > This short tutorial assumes that **Webpack 5** is the bundler used in the application where `netgrep` will be integrated. A complete example is available [in the `example` package](https://github.com/dgopsq/netgrep/tree/main/packages/example). 21 | 22 | First of all install the module: 23 | 24 | ```bash 25 | # Using yarn 26 | yarn add @netgrep/netgrep 27 | 28 | # Using npm 29 | npm install @netgrep/netgrep 30 | ``` 31 | 32 | The [`asyncWebAssembbly` experiment flag](https://webpack.js.org/configuration/experiments/) should be enabled inside the `webpack.config.js`: 33 | 34 | ```js 35 | module.exports = { 36 | //... 37 | experiments: { 38 | // ... 39 | asyncWebAssembly: true, 40 | }, 41 | }; 42 | ``` 43 | 44 | Then it will be possible to execute `netgrep` directly while the bundled WASM file will be loaded asynchronously in the background: 45 | 46 | ```ts 47 | import { Netgrep } from '@netgrep/netgrep'; 48 | 49 | // Create a Netgrep instance, here it's 50 | // possible to pass an initial configuration. 51 | const NG = new Netgrep(); 52 | 53 | // Execute a Netgrep search on the url using 54 | // the given pattern. 55 | NG.search("url", "pattern") 56 | .then((output) => { 57 | console.log('The pattern has matched', output.result) 58 | }); 59 | 60 | // It's possible to pass custom metadata during 61 | // the search. These will be returned back in the result 62 | // for convenience. 63 | NG.search("url", "pattern", { post }) 64 | .then((output) => { 65 | console.log('The pattern has matched', output.result) 66 | console.log('Metadata', output.metadata) 67 | }); 68 | 69 | // There is a convenient method to do batched searches 70 | // to multiple urls. Using `searchBatch` the resulting `Promise` 71 | // will resolve only after the completion of all the searches. 72 | NG.searchBatch([ 73 | { url: 'url1' }, 74 | { url: 'url2' }, 75 | { url: 'url3' } 76 | ], "pattern") 77 | .then((outputs) => { 78 | outputs.forEach((output) => { 79 | console.log(`The pattern has matched for ${output.url}`, output.result) 80 | }); 81 | }); 82 | 83 | // If you want to avoid waiting for the completion of 84 | // all the searches, the method `searchBatchWithCallback` will 85 | // execute a callback every time a search completes. 86 | NG.searchBatchWithCallback([ 87 | { url: 'url1' }, 88 | { url: 'url2' }, 89 | { url: 'url3' } 90 | ], "pattern", (output) => { 91 | console.log(`The pattern has matched for ${output.url}`, output.result) 92 | }); 93 | ``` 94 | 95 | ## License 96 | 97 | Netgrep is under the [MIT license](https://github.com/dgopsq/netgrep/blob/main/LICENSE). 98 | -------------------------------------------------------------------------------- /packages/netgrep/src/lib/Netgrep.spec.ts: -------------------------------------------------------------------------------- 1 | import { Netgrep } from './Netgrep'; 2 | import { ReadableStream } from 'node:stream/web'; 3 | import { BatchNetgrepResult } from './data/BatchNetgrepResult'; 4 | 5 | /** 6 | * Helper function to generate a `ReadableStream` 7 | * from an input string. 8 | */ 9 | export function genReadableStreamFromString(str: string): ReadableStream { 10 | return new ReadableStream({ 11 | start(controller) { 12 | controller.enqueue(str); 13 | controller.close(); 14 | }, 15 | }); 16 | } 17 | 18 | const mockSearch = jest.fn(); 19 | const mockFetch = jest.fn(); 20 | 21 | // Moking `search` function. 22 | jest.mock('@netgrep/search', () => { 23 | return { search_bytes: () => mockSearch() }; 24 | }); 25 | 26 | // Mocking `fetch` function. 27 | global.fetch = mockFetch; 28 | 29 | describe('Netgrep', () => { 30 | describe('Netgrep::search', () => { 31 | const NG = new Netgrep({ enableMemoryCache: false }); 32 | const NGWithCache = new Netgrep({ enableMemoryCache: true }); 33 | 34 | const url = 'url'; 35 | const pattern = 'pattern'; 36 | 37 | beforeEach(() => { 38 | mockFetch.mockClear(); 39 | 40 | mockFetch.mockImplementation(() => 41 | Promise.resolve({ body: genReadableStreamFromString('test') }) 42 | ); 43 | }); 44 | 45 | it('should work for a positive search result', async () => { 46 | mockSearch.mockReturnValue(true); 47 | 48 | const result = await NG.search(url, pattern); 49 | 50 | expect(result).toMatchObject({ url, result: true }); 51 | }); 52 | 53 | it('should work for a negative search result', async () => { 54 | mockSearch.mockReturnValue(false); 55 | 56 | const result = await NG.search(url, pattern); 57 | 58 | expect(result).toMatchObject({ url, result: false }); 59 | }); 60 | 61 | it('should work with the in-memory cache active', async () => { 62 | mockSearch.mockReturnValue(true); 63 | 64 | const result = await NGWithCache.search(url, pattern); 65 | 66 | expect(mockFetch).toBeCalledTimes(1); 67 | expect(result).toMatchObject({ url, result: true }); 68 | 69 | const result2 = await NGWithCache.search(url, pattern); 70 | 71 | expect(mockFetch).toBeCalledTimes(1); 72 | expect(result2).toMatchObject({ url, result: true }); 73 | }); 74 | }); 75 | 76 | describe('Netgrep::searchBatch', () => { 77 | const NG = new Netgrep({ enableMemoryCache: false }); 78 | const NGWithCache = new Netgrep({ enableMemoryCache: true }); 79 | 80 | const urls = [{ url: 'url1' }, { url: 'url2' }, { url: 'url3' }]; 81 | 82 | const pattern = 'pattern'; 83 | 84 | beforeEach(() => { 85 | mockFetch.mockClear(); 86 | 87 | mockFetch.mockImplementation(() => 88 | Promise.resolve({ body: genReadableStreamFromString('test') }) 89 | ); 90 | }); 91 | 92 | it('should work for a positive search result', async () => { 93 | mockSearch.mockReturnValue(true); 94 | 95 | const results = await NG.searchBatch(urls, pattern); 96 | 97 | const expectedResults: Array = urls.map( 98 | ({ url }) => ({ 99 | url, 100 | pattern, 101 | result: true, 102 | error: null, 103 | }) 104 | ); 105 | 106 | expect(results).toMatchObject(expectedResults); 107 | }); 108 | 109 | it('should work for a negative search result', async () => { 110 | mockSearch.mockReturnValue(false); 111 | 112 | const results = await NG.searchBatch(urls, pattern); 113 | 114 | const expectedResults: Array = urls.map( 115 | ({ url }) => ({ 116 | url, 117 | pattern, 118 | result: false, 119 | error: null, 120 | }) 121 | ); 122 | 123 | expect(results).toMatchObject(expectedResults); 124 | }); 125 | 126 | it('should handle errors in the fetch requests', async () => { 127 | const errorMessage = 'message'; 128 | 129 | mockFetch.mockImplementation(() => 130 | Promise.reject(new Error(errorMessage)) 131 | ); 132 | 133 | mockSearch.mockReturnValue(false); 134 | 135 | const results = await NG.searchBatch(urls, pattern); 136 | 137 | const expectedResults: Array = urls.map( 138 | ({ url }) => ({ 139 | url, 140 | pattern, 141 | result: false, 142 | error: errorMessage, 143 | }) 144 | ); 145 | 146 | expect(results).toMatchObject(expectedResults); 147 | }); 148 | 149 | it('should work with the in-memory cache active', async () => { 150 | mockSearch.mockReturnValue(true); 151 | 152 | const results = await NGWithCache.searchBatch(urls, pattern); 153 | 154 | const expectedResults: Array = urls.map( 155 | ({ url }) => ({ 156 | url, 157 | pattern, 158 | result: true, 159 | error: null, 160 | }) 161 | ); 162 | 163 | expect(mockFetch).toBeCalledTimes(urls.length); 164 | expect(results).toMatchObject(expectedResults); 165 | 166 | const results2 = await NGWithCache.searchBatch(urls, pattern); 167 | 168 | expect(mockFetch).toBeCalledTimes(urls.length); 169 | expect(results2).toMatchObject(expectedResults); 170 | }); 171 | }); 172 | }); 173 | -------------------------------------------------------------------------------- /packages/netgrep/src/lib/Netgrep.ts: -------------------------------------------------------------------------------- 1 | import { search_bytes } from '@netgrep/search'; 2 | import { BatchNetgrepResult } from './data/BatchNetgrepResult.js'; 3 | import { NetgrepConfig } from './data/NetgrepConfig.js'; 4 | import { NetgrepInput } from './data/NetgrepInput.js'; 5 | import { NetgrepResult } from './data/NetgrepResult.js'; 6 | import { NetgrepSearchConfig } from './data/NetgrepSearchConfig.js'; 7 | 8 | /** 9 | * The default configuration used by `netgrep`. 10 | */ 11 | const defaultConfig: NetgrepConfig = { 12 | enableMemoryCache: true, 13 | }; 14 | 15 | /** 16 | * The `netgrep` library allows to search remote files 17 | * for a specific pattern using the `ripgrep` library 18 | * over HTTP. 19 | */ 20 | export class Netgrep { 21 | private readonly config: NetgrepConfig; 22 | private readonly memoryCache: Record = {}; 23 | 24 | constructor(config?: Partial) { 25 | this.config = { 26 | ...defaultConfig, 27 | ...config, 28 | }; 29 | } 30 | 31 | /** 32 | * Search a remote file for a specific pattern. 33 | * This method uses `ripgrep` under the hood in order to 34 | * start searching while downloading the file instead of 35 | * waiting for the whole file to be available offline. 36 | * 37 | * @param url 38 | * The url to the remote file. 39 | * @param pattern 40 | * The pattern to search for. This can be anything `ripgrep` can understand. 41 | * @param metadata 42 | * An optional object that will be returned back as soon as a match 43 | * as been found in the file. 44 | * @param config 45 | * An optional configuration respecting the `NetgrepSearchConfig` type. 46 | * @returns 47 | * A promise resolving to a `NetgrepResult` as soon as a match will 48 | * be found in the remote file. 49 | */ 50 | public search( 51 | url: string, 52 | pattern: string, 53 | metadata?: T, 54 | config?: NetgrepSearchConfig 55 | ): Promise> { 56 | return new Promise((resolve, reject) => { 57 | const handleReader = ( 58 | reader: ReadableStreamDefaultReader 59 | ) => { 60 | return reader.read().then(({ value, done }) => { 61 | // If the reader is actually done 62 | // let's quit this job returning `false`. 63 | if (done) { 64 | resolve({ url, pattern, result: false, metadata }); 65 | return; 66 | } 67 | 68 | // Execute the search in the current chunk of bytes 69 | // using the underneath WASM core module. 70 | const u8Array = new Uint8Array(value); 71 | const result = search_bytes(u8Array, pattern); 72 | 73 | // Store the `Uint8Array` in the memory cache 74 | // if it's enabled. 75 | if (this.config.enableMemoryCache) { 76 | this.upsertMemoryCache(url, u8Array); 77 | } 78 | 79 | if (result) { 80 | resolve({ url, pattern, result: true, metadata }); 81 | } else { 82 | handleReader(reader); 83 | } 84 | }); 85 | }; 86 | 87 | // Search the content in the memory cache 88 | // if it's enabled. 89 | if (this.config.enableMemoryCache && this.memoryCache[url]) { 90 | const result = search_bytes(this.memoryCache[url], pattern); 91 | resolve({ url, pattern, result, metadata }); 92 | return; 93 | } 94 | 95 | fetch(url, { signal: config?.signal }) 96 | .then((res) => 97 | !res.body 98 | ? Promise.reject(new Error("The response doesn't contain a body")) 99 | : Promise.resolve(res.body.getReader()) 100 | ) 101 | .then(handleReader) 102 | .catch(reject); 103 | }); 104 | } 105 | 106 | /** 107 | * Execute the `search` method in batch for multiple 108 | * files. This method returns a promise waiting for all 109 | * the executed searches to complete. 110 | * 111 | * @param urls 112 | * An array of `NetgrepInput` containing the urls to the 113 | * files. `T` is the generic type for the optional metadata to 114 | * pass for each url. 115 | * @param pattern 116 | * The pattern to search for. This can be anything `ripgrep` can understand. 117 | * @param config 118 | * An optional configuration respecting the `NetgrepSearchConfig` type. 119 | * @returns 120 | * A promise waiting for all the executed searches to complete. 121 | */ 122 | public searchBatch( 123 | inputs: Array>, 124 | pattern: string, 125 | config?: NetgrepSearchConfig 126 | ): Promise>> { 127 | return Promise.all( 128 | inputs.map((input) => { 129 | const { url } = input; 130 | 131 | return this.search(url, pattern, input.metadata, config) 132 | .then((res) => ({ ...res, error: null })) 133 | .catch((err) => ({ 134 | url, 135 | result: false, 136 | pattern, 137 | metadata: input.metadata, 138 | error: this.serializeError(err), 139 | })); 140 | }) 141 | ); 142 | } 143 | 144 | /** 145 | * Execute the `search` method in batch for multiple 146 | * files. This method takes a callback as an input and 147 | * executes it everytime a match happens. 148 | * 149 | * @param urls 150 | * An array of `NetgrepInput` containing the urls to the 151 | * files. `T` is the generic type for the optional metadata to 152 | * pass for each url. 153 | * @param pattern 154 | * The pattern to search for. This can be anything `ripgrep` can understand. 155 | * @param cb 156 | * The callback that will be triggered at every match. It takes 157 | * a `BatchNetgrepResult` as a parameter. 158 | * @param config 159 | * An optional configuration respecting the `NetgrepSearchConfig` type. 160 | */ 161 | public searchBatchWithCallback( 162 | inputs: Array>, 163 | pattern: string, 164 | cb: (result: BatchNetgrepResult) => void, 165 | config?: NetgrepSearchConfig 166 | ): void { 167 | inputs.forEach((input) => { 168 | const { url } = input; 169 | this.search(url, pattern, input.metadata, config) 170 | .then((res) => cb({ ...res, error: null })) 171 | .catch((err) => 172 | cb({ 173 | url, 174 | result: false, 175 | pattern, 176 | metadata: input.metadata, 177 | error: this.serializeError(err), 178 | }) 179 | ); 180 | }); 181 | } 182 | 183 | /** 184 | * Transform an `unknown` type returned from a catch 185 | * into a `string`. 186 | */ 187 | private serializeError(err: unknown): string { 188 | if (err instanceof Error) { 189 | return err.message; 190 | } else { 191 | return JSON.stringify(err); 192 | } 193 | } 194 | 195 | /** 196 | * Upsert a slice of bytes into the in-memory cache. 197 | */ 198 | private upsertMemoryCache(url: string, bytes: Uint8Array) { 199 | const currentBlockLength = this.memoryCache[url]?.length || 0; 200 | const joinedArray = new Uint8Array(currentBlockLength + bytes.length); 201 | 202 | if (this.memoryCache[url]) joinedArray.set(this.memoryCache[url]); 203 | 204 | joinedArray.set(bytes, currentBlockLength); 205 | this.memoryCache[url] = joinedArray; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi", 21 | "libc", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "base64" 27 | version = "0.13.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 30 | 31 | [[package]] 32 | name = "bstr" 33 | version = "0.2.17" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" 36 | dependencies = [ 37 | "lazy_static", 38 | "memchr", 39 | "regex-automata", 40 | ] 41 | 42 | [[package]] 43 | name = "bumpalo" 44 | version = "3.10.0" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" 47 | 48 | [[package]] 49 | name = "bytecount" 50 | version = "0.6.3" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" 53 | 54 | [[package]] 55 | name = "cfg-if" 56 | version = "0.1.10" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 59 | 60 | [[package]] 61 | name = "cfg-if" 62 | version = "1.0.0" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 65 | 66 | [[package]] 67 | name = "console_error_panic_hook" 68 | version = "0.1.7" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" 71 | dependencies = [ 72 | "cfg-if 1.0.0", 73 | "wasm-bindgen", 74 | ] 75 | 76 | [[package]] 77 | name = "encoding_rs" 78 | version = "0.8.31" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" 81 | dependencies = [ 82 | "cfg-if 1.0.0", 83 | ] 84 | 85 | [[package]] 86 | name = "encoding_rs_io" 87 | version = "0.1.7" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83" 90 | dependencies = [ 91 | "encoding_rs", 92 | ] 93 | 94 | [[package]] 95 | name = "fnv" 96 | version = "1.0.7" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 99 | 100 | [[package]] 101 | name = "globset" 102 | version = "0.4.7" 103 | source = "git+https://github.com/dgopsq/ripgrep?tag=13.0.0-wasm#c14c72c4cedf83ec0d2d7e89cfdec9175df350c2" 104 | dependencies = [ 105 | "aho-corasick", 106 | "bstr", 107 | "fnv", 108 | "log", 109 | "regex", 110 | ] 111 | 112 | [[package]] 113 | name = "grep" 114 | version = "0.2.8" 115 | source = "git+https://github.com/dgopsq/ripgrep?tag=13.0.0-wasm#c14c72c4cedf83ec0d2d7e89cfdec9175df350c2" 116 | dependencies = [ 117 | "grep-cli", 118 | "grep-matcher", 119 | "grep-printer", 120 | "grep-regex", 121 | "grep-searcher", 122 | ] 123 | 124 | [[package]] 125 | name = "grep-cli" 126 | version = "0.1.6" 127 | source = "git+https://github.com/dgopsq/ripgrep?tag=13.0.0-wasm#c14c72c4cedf83ec0d2d7e89cfdec9175df350c2" 128 | dependencies = [ 129 | "atty", 130 | "bstr", 131 | "globset", 132 | "lazy_static", 133 | "log", 134 | "regex", 135 | "same-file", 136 | "termcolor", 137 | "winapi-util", 138 | ] 139 | 140 | [[package]] 141 | name = "grep-matcher" 142 | version = "0.1.5" 143 | source = "git+https://github.com/dgopsq/ripgrep?tag=13.0.0-wasm#c14c72c4cedf83ec0d2d7e89cfdec9175df350c2" 144 | dependencies = [ 145 | "memchr", 146 | ] 147 | 148 | [[package]] 149 | name = "grep-printer" 150 | version = "0.1.6" 151 | source = "git+https://github.com/dgopsq/ripgrep?tag=13.0.0-wasm#c14c72c4cedf83ec0d2d7e89cfdec9175df350c2" 152 | dependencies = [ 153 | "base64", 154 | "bstr", 155 | "grep-matcher", 156 | "grep-searcher", 157 | "instant", 158 | "serde", 159 | "serde_json", 160 | "termcolor", 161 | ] 162 | 163 | [[package]] 164 | name = "grep-regex" 165 | version = "0.1.9" 166 | source = "git+https://github.com/dgopsq/ripgrep?tag=13.0.0-wasm#c14c72c4cedf83ec0d2d7e89cfdec9175df350c2" 167 | dependencies = [ 168 | "aho-corasick", 169 | "bstr", 170 | "grep-matcher", 171 | "log", 172 | "regex", 173 | "regex-syntax", 174 | "thread_local", 175 | ] 176 | 177 | [[package]] 178 | name = "grep-searcher" 179 | version = "0.1.8" 180 | source = "git+https://github.com/dgopsq/ripgrep?tag=13.0.0-wasm#c14c72c4cedf83ec0d2d7e89cfdec9175df350c2" 181 | dependencies = [ 182 | "bstr", 183 | "bytecount", 184 | "encoding_rs", 185 | "encoding_rs_io", 186 | "grep-matcher", 187 | "log", 188 | "memmap2", 189 | ] 190 | 191 | [[package]] 192 | name = "hermit-abi" 193 | version = "0.1.19" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 196 | dependencies = [ 197 | "libc", 198 | ] 199 | 200 | [[package]] 201 | name = "instant" 202 | version = "0.1.12" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 205 | dependencies = [ 206 | "cfg-if 1.0.0", 207 | "js-sys", 208 | "wasm-bindgen", 209 | "web-sys", 210 | ] 211 | 212 | [[package]] 213 | name = "itoa" 214 | version = "1.0.3" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" 217 | 218 | [[package]] 219 | name = "js-sys" 220 | version = "0.3.59" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" 223 | dependencies = [ 224 | "wasm-bindgen", 225 | ] 226 | 227 | [[package]] 228 | name = "lazy_static" 229 | version = "1.4.0" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 232 | 233 | [[package]] 234 | name = "libc" 235 | version = "0.2.131" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "04c3b4822ccebfa39c02fc03d1534441b22ead323fa0f48bb7ddd8e6ba076a40" 238 | 239 | [[package]] 240 | name = "log" 241 | version = "0.4.17" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 244 | dependencies = [ 245 | "cfg-if 1.0.0", 246 | ] 247 | 248 | [[package]] 249 | name = "memchr" 250 | version = "2.5.0" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 253 | 254 | [[package]] 255 | name = "memmap2" 256 | version = "0.3.1" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357" 259 | dependencies = [ 260 | "libc", 261 | ] 262 | 263 | [[package]] 264 | name = "memory_units" 265 | version = "0.4.0" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" 268 | 269 | [[package]] 270 | name = "once_cell" 271 | version = "1.13.0" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" 274 | 275 | [[package]] 276 | name = "proc-macro2" 277 | version = "1.0.43" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" 280 | dependencies = [ 281 | "unicode-ident", 282 | ] 283 | 284 | [[package]] 285 | name = "quote" 286 | version = "1.0.21" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 289 | dependencies = [ 290 | "proc-macro2", 291 | ] 292 | 293 | [[package]] 294 | name = "regex" 295 | version = "1.6.0" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 298 | dependencies = [ 299 | "aho-corasick", 300 | "memchr", 301 | "regex-syntax", 302 | ] 303 | 304 | [[package]] 305 | name = "regex-automata" 306 | version = "0.1.10" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 309 | 310 | [[package]] 311 | name = "regex-syntax" 312 | version = "0.6.27" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 315 | 316 | [[package]] 317 | name = "ryu" 318 | version = "1.0.11" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 321 | 322 | [[package]] 323 | name = "same-file" 324 | version = "1.0.6" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 327 | dependencies = [ 328 | "winapi-util", 329 | ] 330 | 331 | [[package]] 332 | name = "scoped-tls" 333 | version = "1.0.0" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" 336 | 337 | [[package]] 338 | name = "search" 339 | version = "0.1.5" 340 | dependencies = [ 341 | "grep", 342 | "wasm-bindgen", 343 | "wasm-bindgen-test", 344 | "wee_alloc", 345 | ] 346 | 347 | [[package]] 348 | name = "serde" 349 | version = "1.0.143" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" 352 | dependencies = [ 353 | "serde_derive", 354 | ] 355 | 356 | [[package]] 357 | name = "serde_derive" 358 | version = "1.0.143" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" 361 | dependencies = [ 362 | "proc-macro2", 363 | "quote", 364 | "syn", 365 | ] 366 | 367 | [[package]] 368 | name = "serde_json" 369 | version = "1.0.83" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" 372 | dependencies = [ 373 | "itoa", 374 | "ryu", 375 | "serde", 376 | ] 377 | 378 | [[package]] 379 | name = "syn" 380 | version = "1.0.99" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" 383 | dependencies = [ 384 | "proc-macro2", 385 | "quote", 386 | "unicode-ident", 387 | ] 388 | 389 | [[package]] 390 | name = "termcolor" 391 | version = "1.1.3" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 394 | dependencies = [ 395 | "winapi-util", 396 | ] 397 | 398 | [[package]] 399 | name = "thread_local" 400 | version = "1.1.4" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 403 | dependencies = [ 404 | "once_cell", 405 | ] 406 | 407 | [[package]] 408 | name = "unicode-ident" 409 | version = "1.0.3" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" 412 | 413 | [[package]] 414 | name = "wasm-bindgen" 415 | version = "0.2.82" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" 418 | dependencies = [ 419 | "cfg-if 1.0.0", 420 | "wasm-bindgen-macro", 421 | ] 422 | 423 | [[package]] 424 | name = "wasm-bindgen-backend" 425 | version = "0.2.82" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" 428 | dependencies = [ 429 | "bumpalo", 430 | "log", 431 | "once_cell", 432 | "proc-macro2", 433 | "quote", 434 | "syn", 435 | "wasm-bindgen-shared", 436 | ] 437 | 438 | [[package]] 439 | name = "wasm-bindgen-futures" 440 | version = "0.4.32" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" 443 | dependencies = [ 444 | "cfg-if 1.0.0", 445 | "js-sys", 446 | "wasm-bindgen", 447 | "web-sys", 448 | ] 449 | 450 | [[package]] 451 | name = "wasm-bindgen-macro" 452 | version = "0.2.82" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" 455 | dependencies = [ 456 | "quote", 457 | "wasm-bindgen-macro-support", 458 | ] 459 | 460 | [[package]] 461 | name = "wasm-bindgen-macro-support" 462 | version = "0.2.82" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" 465 | dependencies = [ 466 | "proc-macro2", 467 | "quote", 468 | "syn", 469 | "wasm-bindgen-backend", 470 | "wasm-bindgen-shared", 471 | ] 472 | 473 | [[package]] 474 | name = "wasm-bindgen-shared" 475 | version = "0.2.82" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" 478 | 479 | [[package]] 480 | name = "wasm-bindgen-test" 481 | version = "0.3.32" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "513df541345bb9fcc07417775f3d51bbb677daf307d8035c0afafd87dc2e6599" 484 | dependencies = [ 485 | "console_error_panic_hook", 486 | "js-sys", 487 | "scoped-tls", 488 | "wasm-bindgen", 489 | "wasm-bindgen-futures", 490 | "wasm-bindgen-test-macro", 491 | ] 492 | 493 | [[package]] 494 | name = "wasm-bindgen-test-macro" 495 | version = "0.3.32" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "6150d36a03e90a3cf6c12650be10626a9902d70c5270fd47d7a47e5389a10d56" 498 | dependencies = [ 499 | "proc-macro2", 500 | "quote", 501 | ] 502 | 503 | [[package]] 504 | name = "web-sys" 505 | version = "0.3.59" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" 508 | dependencies = [ 509 | "js-sys", 510 | "wasm-bindgen", 511 | ] 512 | 513 | [[package]] 514 | name = "wee_alloc" 515 | version = "0.4.5" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" 518 | dependencies = [ 519 | "cfg-if 0.1.10", 520 | "libc", 521 | "memory_units", 522 | "winapi", 523 | ] 524 | 525 | [[package]] 526 | name = "winapi" 527 | version = "0.3.9" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 530 | dependencies = [ 531 | "winapi-i686-pc-windows-gnu", 532 | "winapi-x86_64-pc-windows-gnu", 533 | ] 534 | 535 | [[package]] 536 | name = "winapi-i686-pc-windows-gnu" 537 | version = "0.4.0" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 540 | 541 | [[package]] 542 | name = "winapi-util" 543 | version = "0.1.5" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 546 | dependencies = [ 547 | "winapi", 548 | ] 549 | 550 | [[package]] 551 | name = "winapi-x86_64-pc-windows-gnu" 552 | version = "0.4.0" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 555 | -------------------------------------------------------------------------------- /packages/example/assets/veil.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | THE ADVENTURE OF THE VEILED LODGER 6 | 7 | Arthur Conan Doyle 8 | 9 | 10 | 11 | When one considers that Mr. Sherlock Holmes was in active practice 12 | for twenty-three years, and that during seventeen of these I was 13 | allowed to cooperate with him and to keep notes of his doings, it 14 | will be clear that I have a mass of material at my command. The 15 | problem has always been not to find but to choose. There is the long 16 | row of year-books which fill a shelf, and there are the 17 | dispatch-cases filled with documents, a perfect quarry for the 18 | student not only of crime but of the social and official scandals of 19 | the late Victorian era. Concerning these latter, I may say that the 20 | writers of agonized letters, who beg that the honour of their 21 | families or the reputation of famous forebears may not be touched, 22 | have nothing to fear. The discretion and high sense of professional 23 | honour which have always distinguished my friend are still at work in 24 | the choice of these memoirs, and no confidence will be abused. I 25 | deprecate, however, in the strongest way the attempts which have been 26 | made lately to get at and to destroy these papers. The source of 27 | these outrages is known, and if they are repeated I have Mr. Holmes's 28 | authority for saying that the whole story concerning the politician, 29 | the lighthouse, and the trained cormorant will be given to the 30 | public. There is at least one reader who will understand. 31 | 32 | It is not reasonable to suppose that every one of these cases gave 33 | Holmes the opportunity of showing those curious gifts of instinct and 34 | observation which I have endeavoured to set forth in these memoirs. 35 | Sometimes he had with much effort to pick the fruit, sometimes it 36 | fell easily into his lap. But the most terrible human tragedies were 37 | often involved in those cases which brought him the fewest personal 38 | opportunities, and it is one of these which I now desire to record. 39 | In telling it, I have made a slight change of name and place, but 40 | otherwise the facts are as stated. 41 | 42 | One forenoon--it was late in 1896--I received a hurried note from 43 | Holmes asking for my attendance. When I arrived I found him seated in 44 | a smoke-laden atmosphere, with an elderly, motherly woman of the 45 | buxom landlady type in the corresponding chair in front of him. 46 | 47 | "This is Mrs. Merrilow, of South Brixton," said my friend with a wave 48 | of the hand. "Mrs. Merrilow does not object to tobacco, Watson, if 49 | you wish to indulge your filthy habits. Mrs. Merrilow has an 50 | interesting story to tell which may well lead to further developments 51 | in which your presence may be useful." 52 | 53 | "Anything I can do--" 54 | 55 | "You will understand, Mrs. Merrilow, that if I come to Mrs. Ronder I 56 | should prefer to have a witness. You will make her understand that 57 | before we arrive." 58 | 59 | "Lord bless you, Mr. Holmes," said our visitor, "she is that anxious 60 | to see you that you might bring the whole parish at your heels!" 61 | 62 | "Then we shall come early in the afternoon. Let us see that we have 63 | our facts correct before we start. If we go over them it will help 64 | Dr. Watson to understand the situation. You say that Mrs. Ronder has 65 | been your lodger for seven years and that you have only once seen her 66 | face." 67 | 68 | "And I wish to God I had not!" said Mrs. Merrilow. 69 | 70 | "It was, I understand, terribly mutilated." 71 | 72 | "Well, Mr. Holmes, you would hardly say it was a face at all. That's 73 | how it looked. Our milkman got a glimpse of her once peeping out of 74 | the upper window, and he dropped his tin and the milk all over the 75 | front garden. That is the kind of face it is. When I saw her--I 76 | happened on her unawares--she covered up quick, and then she said, 77 | 'Now, Mrs. Merrilow, you know at last why it is that I never raise my 78 | veil.'" 79 | 80 | "Do you know anything about her history?" 81 | 82 | "Nothing at all." 83 | 84 | "Did she give references when she came?" 85 | 86 | "No, sir, but she gave hard cash, and plenty of it. A quarter's rent 87 | right down on the table in advance and no arguing about terms. In 88 | these times a poor woman like me can't afford to turn down a chance 89 | like that." 90 | 91 | "Did she give any reason for choosing your house?" 92 | 93 | "Mine stands well back from the road and is more private than most. 94 | Then, again, I only take the one, and I have no family of my own. I 95 | reckon she had tried others and found that mine suited her best. It's 96 | privacy she is after, and she is ready to pay for it." 97 | 98 | "You say that she never showed her face from first to last save on 99 | the one accidental occasion. Well, it is a very remarkable story, 100 | most remarkable, and I don't wonder that you want it examined." 101 | 102 | "I don't, Mr. Holmes. I am quite satisfied so long as I get my rent. 103 | You could not have a quieter lodger, or one who gives less trouble." 104 | 105 | "Then what has brought matters to a head?" 106 | 107 | "Her health, Mr. Holmes. She seems to be wasting away. And there's 108 | something terrible on her mind. 'Murder!' she cries. 'Murder!' And 109 | once I heard her: 'You cruel beast! You monster!' she cried. It was 110 | in the night, and it fair rang through the house and sent the shivers 111 | through me. So I went to her in the morning. 'Mrs. Ronder,' I says, 112 | 'if you have anything that is troubling your soul, there's the 113 | clergy,' I says, 'and there's the police. Between them you should get 114 | some help.' 'For God's sake, not the police!' says she, 'and the 115 | clergy can't change what is past. And yet,' she says, 'it would ease 116 | my mind if someone knew the truth before I died.' 'Well,' says I, 'if 117 | you won't have the regulars, there is this detective man what we read 118 | about'--beggin' your pardon, Mr. Holmes. And she, she fair jumped at 119 | it. 'That's the man,' says she. 'I wonder I never thought of it 120 | before. Bring him here, Mrs. Merrilow, and if he won't come, tell him 121 | I am the wife of Ronder's wild beast show. Say that, and give him the 122 | name Abbas Parva. Here it is as she wrote it, Abbas Parva. 'That will 123 | bring him if he's the man I think he is.'" 124 | 125 | "And it will, too," remarked Holmes. "Very good, Mrs. Merrilow. I 126 | should like to have a little chat with Dr. Watson. That will carry us 127 | till lunch-time. About three o'clock you may expect to see us at your 128 | house in Brixton." 129 | 130 | Our visitor had no sooner waddled out of the room--no other verb can 131 | describe Mrs. Merrilow's method of progression--than Sherlock Holmes 132 | threw himself with fierce energy upon the pile of commonplace books 133 | in the corner. For a few minutes there was a constant swish of the 134 | leaves, and then with a grunt of satisfaction he came upon what he 135 | sought. So excited was he that he did not rise, but sat upon the 136 | floor like some strange Buddha, with crossed legs, the huge books all 137 | round him, and one open upon his knees. 138 | 139 | "The case worried me at the time, Watson. Here are my marginal notes 140 | to prove it. I confess that I could make nothing of it. And yet I was 141 | convinced that the coroner was wrong. Have you no recollection of the 142 | Abbas Parva tragedy?" 143 | 144 | "None, Holmes." 145 | 146 | "And yet you were with me then. But certainly my own impression was 147 | very superficial. For there was nothing to go by, and none of the 148 | parties had engaged my services. Perhaps you would care to read the 149 | papers?" 150 | 151 | "Could you not give me the points?" 152 | 153 | "That is very easily done. It will probably come back to your memory 154 | as I talk. Ronder, of course, was a household word. He was the rival 155 | of Wombwell, and of Sanger, one of the greatest showmen of his day. 156 | There is evidence, however, that he took to drink, and that both he 157 | and his show were on the down grade at the time of the great tragedy. 158 | The caravan had halted for the night at Abbas Parva, which is a small 159 | village in Berkshire, when this horror occurred. They were on their 160 | way to Wimbledon, travelling by road, and they were simply camping 161 | and not exhibiting, as the place is so small a one that it would not 162 | have paid them to open. 163 | 164 | "They had among their exhibits a very fine North African lion. Sahara 165 | King was its name, and it was the habit, both of Ronder and his wife, 166 | to give exhibitions inside its cage. Here, you see, is a photograph 167 | of the performance by which you will perceive that Ronder was a huge 168 | porcine person and that his wife was a very magnificent woman. It was 169 | deposed at the inquest that there had been some signs that the lion 170 | was dangerous, but, as usual, familiarity begat contempt, and no 171 | notice was taken of the fact. 172 | 173 | "It was usual for either Ronder or his wife to feed the lion at 174 | night. Sometimes one went, sometimes both, but they never allowed 175 | anyone else to do it, for they believed that so long as they were the 176 | food-carriers he would regard them as benefactors and would never 177 | molest them. On this particular night, seven years ago, they both 178 | went, and a very terrible happening followed, the details of which 179 | have never been made clear. 180 | 181 | "It seems that the whole camp was roused near midnight by the roars 182 | of the animal and the screams of the woman. The different grooms and 183 | employees rushed from their tents, carrying lanterns, and by their 184 | light an awful sight was revealed. Ronder lay, with the back of his 185 | head crushed in and deep claw-marks across his scalp, some ten yards 186 | from the cage, which was open. Close to the door of the cage lay Mrs. 187 | Ronder upon her back, with the creature squatting and snarling above 188 | her. It had torn her face in such a fashion that it was never thought 189 | that she could live. Several of the circus men, headed by Leonardo, 190 | the strong man, and Griggs, the clown, drove the creature off with 191 | poles, upon which it sprang back into the cage and was at once locked 192 | in. How it had got loose was a mystery. It was conjectured that the 193 | pair intended to enter the cage, but that when the door was loosed 194 | the creature bounded out upon them. There was no other point of 195 | interest in the evidence save that the woman in a delirium of agony 196 | kept screaming, 'Coward! Coward!' as she was carried back to the van 197 | in which they lived. It was six months before she was fit to give 198 | evidence, but the inquest was duly held, with the obvious verdict of 199 | death from misadventure." 200 | 201 | "What alternative could be conceived?" said I. 202 | 203 | "You may well say so. And yet there were one or two points which 204 | worried young Edmunds, of the Berkshire Constabulary. A smart lad 205 | that! He was sent later to Allahabad. That was how I came into the 206 | matter, for he dropped in and smoked a pipe or two over it." 207 | 208 | "A thin, yellow-haired man?" 209 | 210 | "Exactly. I was sure you would pick up the trail presently." 211 | 212 | "But what worried him?" 213 | 214 | "Well, we were both worried. It was so deucedly difficult to 215 | reconstruct the affair. Look at it from the lion's point of view. He 216 | is liberated. What does he do? He takes half a dozen bounds forward, 217 | which brings him to Ronder. Ronder turns to fly--the claw-marks were 218 | on the back of his head--but the lion strikes him down. Then, instead 219 | of bounding on and escaping, he returns to the woman, who was close 220 | to the cage, and he knocks her over and chews her face up. Then, 221 | again, those cries of hers would seem to imply that her husband had 222 | in some way failed her. What could the poor devil have done to help 223 | her? You see the difficulty?" 224 | 225 | "Quite." 226 | 227 | "And then there was another thing. It comes back to me now as I think 228 | it over. There was some evidence that just at the time the lion 229 | roared and the woman screamed, a man began shouting in terror." 230 | 231 | "This man Ronder, no doubt." 232 | 233 | "Well, if his skull was smashed in you would hardly expect to hear 234 | from him again. There were at least two witnesses who spoke of the 235 | cries of a man being mingled with those of a woman." 236 | 237 | "I should think the whole camp was crying out by then. As to the 238 | other points, I think I could suggest a solution." 239 | 240 | "I should be glad to consider it." 241 | 242 | "The two were together, ten yards from the cage, when the lion got 243 | loose. The man turned and was struck down. The woman conceived the 244 | idea of getting into the cage and shutting the door. It was her only 245 | refuge. She made for it, and just as she reached it the beast bounded 246 | after her and knocked her over. She was angry with her husband for 247 | having encouraged the beast's rage by turning. If they had faced it 248 | they might have cowed it. Hence her cries of 'Coward!'" 249 | 250 | "Brilliant, Watson! Only one flaw in your diamond." 251 | 252 | "What is the flaw, Holmes?" 253 | 254 | "If they were both ten paces from the cage, how came the beast to get 255 | loose?" 256 | 257 | "Is it possible that they had some enemy who loosed it?" 258 | 259 | "And why should it attack them savagely when it was in the habit of 260 | playing with them, and doing tricks with them inside the cage?" 261 | 262 | "Possibly the same enemy had done something to enrage it." 263 | 264 | Holmes looked thoughtful and remained in silence for some moments. 265 | 266 | "Well, Watson, there is this to be said for your theory. Ronder was a 267 | man of many enemies. Edmunds told me that in his cups he was 268 | horrible. A huge bully of a man, he cursed and slashed at everyone 269 | who came in his way. I expect those cries about a monster, of which 270 | our visitor has spoken, were nocturnal reminiscences of the dear 271 | departed. However, our speculations are futile until we have all the 272 | facts. There is a cold partridge on the sideboard, Watson, and a 273 | bottle of Montrachet. Let us renew our energies before we make a 274 | fresh call upon them." 275 | 276 | When our hansom deposited us at the house of Mrs. Merrilow, we found 277 | that plump lady blocking up the open door of her humble but retired 278 | abode. It was very clear that her chief preoccupation was lest she 279 | should lose a valuable lodger, and she implored us, before showing us 280 | up, to say and do nothing which could lead to so undesirable an end. 281 | Then, having reassured her, we followed her up the straight, badly 282 | carpeted staircase and were shown into the room of the mysterious 283 | lodger. 284 | 285 | It was a close, musty, ill-ventilated place, as might be expected, 286 | since its inmate seldom left it. From keeping beasts in a cage, the 287 | woman seemed, by some retribution of fate, to have become herself a 288 | beast in a cage. She sat now in a broken armchair in the shadowy 289 | corner of the room. Long years of inaction had coarsened the lines of 290 | her figure, but at some period it must have been beautiful, and was 291 | still full and voluptuous. A thick dark veil covered her face, but it 292 | was cut off close at her upper lip and disclosed a perfectly shaped 293 | mouth and a delicately rounded chin. I could well conceive that she 294 | had indeed been a very remarkable woman. Her voice, too, was well 295 | modulated and pleasing. 296 | 297 | "My name is not unfamiliar to you, Mr. Holmes," said she. "I thought 298 | that it would bring you." 299 | 300 | "That is so, madam, though I do not know how you are aware that I was 301 | interested in your case." 302 | 303 | "I learned it when I had recovered my health and was examined by Mr. 304 | Edmunds, the county detective. I fear I lied to him. Perhaps it would 305 | have been wiser had I told the truth." 306 | 307 | "It is usually wiser to tell the truth. But why did you lie to him?" 308 | 309 | "Because the fate of someone else depended upon it. I know that he 310 | was a very worthless being, and yet I would not have his destruction 311 | upon my conscience. We had been so close--so close!" 312 | 313 | "But has this impediment been removed?" 314 | 315 | "Yes, sir. The person that I allude to is dead." 316 | 317 | "Then why should you not now tell the police anything you know?" 318 | 319 | "Because there is another person to be considered. That other person 320 | is myself. I could not stand the scandal and publicity which would 321 | come from a police examination. I have not long to live, but I wish 322 | to die undisturbed. And yet I wanted to find one man of judgment to 323 | whom I could tell my terrible story, so that when I am gone all might 324 | be understood." 325 | 326 | "You compliment me, madam. At the same time, I am a responsible 327 | person. I do not promise you that when you have spoken I may not 328 | myself think it my duty to refer the case to the police." 329 | 330 | "I think not, Mr. Holmes. I know your character and methods too well, 331 | for I have followed your work for some years. Reading is the only 332 | pleasure which fate has left me, and I miss little which passes in 333 | the world. But in any case, I will take my chance of the use which 334 | you may make of my tragedy. It will ease my mind to tell it." 335 | 336 | "My friend and I would be glad to hear it." 337 | 338 | The woman rose and took from a drawer the photograph of a man. He was 339 | clearly a professional acrobat, a man of magnificent physique, taken 340 | with his huge arms folded across his swollen chest and a smile 341 | breaking from under his heavy moustache--the self-satisfied smile of 342 | the man of many conquests. 343 | 344 | "That is Leonardo," she said. 345 | 346 | "Leonardo, the strong man, who gave evidence?" 347 | 348 | "The same. And this--this is my husband." 349 | 350 | It was a dreadful face--a human pig, or rather a human wild boar, for 351 | it was formidable in its bestiality. One could imagine that vile 352 | mouth champing and foaming in its rage, and one could conceive those 353 | small, vicious eyes darting pure malignancy as they looked forth upon 354 | the world. Ruffian, bully, beast--it was all written on that 355 | heavy-jowled face. 356 | 357 | "Those two pictures will help you, gentlemen, to understand the 358 | story. I was a poor circus girl brought up on the sawdust, and doing 359 | springs through the hoop before I was ten. When I became a woman this 360 | man loved me, if such lust as his can be called love, and in an evil 361 | moment I became his wife. From that day I was in hell, and he the 362 | devil who tormented me. There was no one in the show who did not know 363 | of his treatment. He deserted me for others. He tied me down and 364 | lashed me with his riding-whip when I complained. They all pitied me 365 | and they all loathed him, but what could they do? They feared him, 366 | one and all. For he was terrible at all times, and murderous when he 367 | was drunk. Again and again he was had up for assault, and for cruelty 368 | to the beasts, but he had plenty of money and the fines were nothing 369 | to him. The best men all left us, and the show began to go downhill. 370 | It was only Leonardo and I who kept it up--with little Jimmy Griggs, 371 | the clown. Poor devil, he had not much to be funny about, but he did 372 | what he could to hold things together. 373 | 374 | "Then Leonardo came more and more into my life. You see what he was 375 | like. I know now the poor spirit that was hidden in that splendid 376 | body, but compared to my husband he seemed like the angel Gabriel. He 377 | pitied me and helped me, till at last our intimacy turned to 378 | love--deep, deep, passionate love, such love as I had dreamed of but 379 | never hoped to feel. My husband suspected it, but I think that he was 380 | a coward as well as a bully, and that Leonardo was the one man that 381 | he was afraid of. He took revenge in his own way by torturing me more 382 | than ever. One night my cries brought Leonardo to the door of our 383 | van. We were near tragedy that night, and soon my lover and I 384 | understood that it could not be avoided. My husband was not fit to 385 | live. We planned that he should die. 386 | 387 | "Leonardo had a clever, scheming brain. It was he who planned it. I 388 | do not say that to blame him, for I was ready to go with him every 389 | inch of the way. But I should never have had the wit to think of such 390 | a plan. We made a club--Leonardo made it--and in the leaden head he 391 | fastened five long steel nails, the points outward, with just such a 392 | spread as the lion's paw. This was to give my husband his death-blow, 393 | and yet to leave the evidence that it was the lion which we would 394 | loose who had done the deed. 395 | 396 | "It was a pitch-dark night when my husband and I went down, as was 397 | our custom, to feed the beast. We carried with us the raw meat in a 398 | zinc pail. Leonardo was waiting at the corner of the big van which we 399 | should have to pass before we reached the cage. He was too slow, and 400 | we walked past him before he could strike, but he followed us on 401 | tiptoe and I heard the crash as the club smashed my husband's skull. 402 | My heart leaped with joy at the sound. I sprang forward, and I undid 403 | the catch which held the door of the great lion's cage. 404 | 405 | "And then the terrible thing happened. You may have heard how quick 406 | these creatures are to scent human blood, and how it excites them. 407 | Some strange instinct had told the creature in one instant that a 408 | human being had been slain. As I slipped the bars it bounded out and 409 | was on me in an instant. Leonardo could have saved me. If he had 410 | rushed forward and struck the beast with his club he might have cowed 411 | it. But the man lost his nerve. I heard him shout in his terror, and 412 | then I saw him turn and fly. At the same instant the teeth of the 413 | lion met in my face. Its hot, filthy breath had already poisoned me 414 | and I was hardly conscious of pain. With the palms of my hands I 415 | tried to push the great steaming, blood-stained jaws away from me, 416 | and I screamed for help. I was conscious that the camp was stirring, 417 | and then dimly I remembered a group of men. Leonardo, Griggs, and 418 | others, dragging me from under the creature's paws. That was my last 419 | memory, Mr. Holmes, for many a weary month. When I came to myself and 420 | saw myself in the mirror, I cursed that lion--oh, how I cursed 421 | him!--not because he had torn away my beauty but because he had not 422 | torn away my life. I had but one desire, Mr. Holmes, and I had enough 423 | money to gratify it. It was that I should cover myself so that my 424 | poor face should be seen by none, and that I should dwell where none 425 | whom I had ever known should find me. That was all that was left to 426 | me to do--and that is what I have done. A poor wounded beast that has 427 | crawled into its hole to die--that is the end of Eugenia Ronder." 428 | 429 | We sat in silence for some time after the unhappy woman had told her 430 | story. Then Holmes stretched out his long arm and patted her hand 431 | with such a show of sympathy as I had seldom known him to exhibit. 432 | 433 | "Poor girl!" he said. "Poor girl! The ways of fate are indeed hard to 434 | understand. If there is not some compensation hereafter, then the 435 | world is a cruel jest. But what of this man Leonardo?" 436 | 437 | "I never saw him or heard from him again. Perhaps I have been wrong 438 | to feel so bitterly against him. He might as soon have loved one of 439 | the freaks whom we carried round the country as the thing which the 440 | lion had left. But a woman's love is not so easily set aside. He had 441 | left me under the beast's claws, he had deserted me in my need, and 442 | yet I could not bring myself to give him to the gallows. For myself, 443 | I cared nothing what became of me. What could be more dreadful than 444 | my actual life? But I stood between Leonardo and his fate." 445 | 446 | "And he is dead?" 447 | 448 | "He was drowned last month when bathing near Margate. I saw his death 449 | in the paper." 450 | 451 | "And what did he do with this five-clawed club, which is the most 452 | singular and ingenious part of all your story?" 453 | 454 | "I cannot tell, Mr. Holmes. There is a chalk-pit by the camp, with a 455 | deep green pool at the base of it. Perhaps in the depths of that 456 | pool--" 457 | 458 | "Well, well, it is of little consequence now. The case is closed." 459 | 460 | "Yes," said the woman, "the case is closed." 461 | 462 | We had risen to go, but there was something in the woman's voice 463 | which arrested Holmes's attention. He turned swiftly upon her. 464 | 465 | "Your life is not your own," he said. "Keep your hands off it." 466 | 467 | "What use is it to anyone?" 468 | 469 | "How can you tell? The example of patient suffering is in itself the 470 | most precious of all lessons to an impatient world." 471 | 472 | The woman's answer was a terrible one. She raised her veil and 473 | stepped forward into the light. 474 | 475 | "I wonder if you would bear it," she said. 476 | 477 | It was horrible. No words can describe the framework of a face when 478 | the face itself is gone. Two living and beautiful brown eyes looking 479 | sadly out from that grisly ruin did but make the view more awful. 480 | Holmes held up his hand in a gesture of pity and protest, and 481 | together we left the room. 482 | 483 | Two days later, when I called upon my friend, he pointed with some 484 | pride to a small blue bottle upon his mantelpiece. I picked it up. 485 | There was a red poison label. A pleasant almondy odour rose when I 486 | opened it. 487 | 488 | "Prussic acid?" said I. 489 | 490 | "Exactly. It came by post. 'I send you my temptation. I will follow 491 | your advice.' That was the message. I think, Watson, we can guess the 492 | name of the brave woman who sent it." 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | ---------- 503 | This text is provided to you "as-is" without any warranty. No 504 | warranties of any kind, expressed or implied, are made to you as to 505 | the text or any medium it may be on, including but not limited to 506 | warranties of merchantablity or fitness for a particular purpose. 507 | 508 | This text was formatted from various free ASCII and HTML variants. 509 | See http://sherlock-holm.es for an electronic form of this text and 510 | additional information about it. 511 | 512 | This text comes from the collection's version 3.1. 513 | 514 | 515 | 516 | 517 | 518 | 519 | -------------------------------------------------------------------------------- /packages/example/assets/reti.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | THE ADVENTURE OF THE RETIRED COLOURMAN 6 | 7 | Arthur Conan Doyle 8 | 9 | 10 | 11 | Sherlock Holmes was in a melancholy and philosophic mood that 12 | morning. His alert practical nature was subject to such reactions. 13 | 14 | "Did you see him?" he asked. 15 | 16 | "You mean the old fellow who has just gone out?" 17 | 18 | "Precisely." 19 | 20 | "Yes, I met him at the door." 21 | 22 | "What did you think of him?" 23 | 24 | "A pathetic, futile, broken creature." 25 | 26 | "Exactly, Watson. Pathetic and futile. But is not all life pathetic 27 | and futile? Is not his story a microcosm of the whole? We reach. We 28 | grasp. And what is left in our hands at the end? A shadow. Or worse 29 | than a shadow--misery." 30 | 31 | "Is he one of your clients?" 32 | 33 | "Well, I suppose I may call him so. He has been sent on by the Yard. 34 | Just as medical men occasionally send their incurables to a quack. 35 | They argue that they can do nothing more, and that whatever happens 36 | the patient can be no worse than he is." 37 | 38 | "What is the matter?" 39 | 40 | Holmes took a rather soiled card from the table. "Josiah Amberley. He 41 | says he was junior partner of Brickfall and Amberley, who are 42 | manufacturers of artistic materials. You will see their names upon 43 | paint-boxes. He made his little pile, retired from business at the 44 | age of sixty-one, bought a house at Lewisham, and settled down to 45 | rest after a life of ceaseless grind. One would think his future was 46 | tolerably assured." 47 | 48 | "Yes, indeed." 49 | 50 | Holmes glanced over some notes which he had scribbled upon the back 51 | of an envelope. 52 | 53 | "Retired in 1896, Watson. Early in 1897 he married a woman twenty 54 | years younger than himself--a good-looking woman, too, if the 55 | photograph does not flatter. A competence, a wife, leisure--it seemed 56 | a straight road which lay before him. And yet within two years he is, 57 | as you have seen, as broken and miserable a creature as crawls 58 | beneath the sun." 59 | 60 | "But what has happened?" 61 | 62 | "The old story, Watson. A treacherous friend and a fickle wife. It 63 | would appear that Amberley has one hobby in life, and it is chess. 64 | Not far from him at Lewisham there lives a young doctor who is also a 65 | chess-player. I have noted his name as Dr. Ray Ernest. Ernest was 66 | frequently in the house, and an intimacy between him and Mrs. 67 | Amberley was a natural sequence, for you must admit that our 68 | unfortunate client has few outward graces, whatever his inner virtues 69 | may be. The couple went off together last week--destination untraced. 70 | What is more, the faithless spouse carried off the old man's deed-box 71 | as her personal luggage with a good part of his life's savings 72 | within. Can we find the lady? Can we save the money? A commonplace 73 | problem so far as it has developed, and yet a vital one for Josiah 74 | Amberley." 75 | 76 | "What will you do about it?" 77 | 78 | "Well, the immediate question, my dear Watson, happens to be, What 79 | will you do?--if you will be good enough to understudy me. You know 80 | that I am preoccupied with this case of the two Coptic Patriarchs, 81 | which should come to a head to-day. I really have not time to go out 82 | to Lewisham, and yet evidence taken on the spot has a special value. 83 | The old fellow was quite insistent that I should go, but I explained 84 | my difficulty. He is prepared to meet a representative." 85 | 86 | "By all means," I answered. "I confess I don't see that I can be of 87 | much service, but I am willing to do my best." And so it was that on 88 | a summer afternoon I set forth to Lewisham, little dreaming that 89 | within a week the affair in which I was engaging would be the eager 90 | debate of all England. 91 | 92 | It was late that evening before I returned to Baker Street and gave 93 | an account of my mission. Holmes lay with his gaunt figure stretched 94 | in his deep chair, his pipe curling forth slow wreaths of acrid 95 | tobacco, while his eyelids drooped over his eyes so lazily that he 96 | might almost have been asleep were it not that at any halt or 97 | questionable passage of my narrative they half lifted, and two gray 98 | eyes, as bright and keen as rapiers, transfixed me with their 99 | searching glance. 100 | 101 | "The Haven is the name of Mr. Josiah Amberley's house," I explained. 102 | "I think it would interest you, Holmes. It is like some penurious 103 | patrician who has sunk into the company of his inferiors. You know 104 | that particular quarter, the monotonous brick streets, the weary 105 | suburban highways. Right in the middle of them, a little island of 106 | ancient culture and comfort, lies this old home, surrounded by a high 107 | sun-baked wall mottled with lichens and topped with moss, the sort of 108 | wall--" 109 | 110 | "Cut out the poetry, Watson," said Holmes severely. "I note that it 111 | was a high brick wall." 112 | 113 | "Exactly. I should not have known which was The Haven had I not asked 114 | a lounger who was smoking in the street. I have a reason for 115 | mentioning him. He was a tall, dark, heavily moustached, rather 116 | military-looking man. He nodded in answer to my inquiry and gave me a 117 | curiously questioning glance, which came back to my memory a little 118 | later. 119 | 120 | "I had hardly entered the gateway before I saw Mr. Amberley coming 121 | down the drive. I only had a glimpse of him this morning, and he 122 | certainly gave me the impression of a strange creature, but when I 123 | saw him in full light his appearance was even more abnormal." 124 | 125 | "I have, of course, studied it, and yet I should be interested to 126 | have your impression," said Holmes. 127 | 128 | "He seemed to me like a man who was literally bowed down by care. His 129 | back was curved as though he carried a heavy burden. Yet he was not 130 | the weakling that I had at first imagined, for his shoulders and 131 | chest have the framework of a giant, though his figure tapers away 132 | into a pair of spindled legs." 133 | 134 | "Left shoe wrinkled, right one smooth." 135 | 136 | "I did not observe that." 137 | 138 | "No, you wouldn't. I spotted his artificial limb. But proceed." 139 | 140 | "I was struck by the snaky locks of grizzled hair which curled from 141 | under his old straw hat, and his face with its fierce, eager 142 | expression and the deeply lined features." 143 | 144 | "Very good, Watson. What did he say?" 145 | 146 | "He began pouring out the story of his grievances. We walked down the 147 | drive together, and of course I took a good look round. I have never 148 | seen a worse-kept place. The garden was all running to seed, giving 149 | me an impression of wild neglect in which the plants had been allowed 150 | to find the way of Nature rather than of art. How any decent woman 151 | could have tolerated such a state of things, I don't know. The house, 152 | too, was slatternly to the last degree, but the poor man seemed 153 | himself to be aware of it and to be trying to remedy it, for a great 154 | pot of green paint stood in the centre of the hall, and he was 155 | carrying a thick brush in his left hand. He had been working on the 156 | woodwork. 157 | 158 | "He took me into his dingy sanctum, and we had a long chat. Of 159 | course, he was disappointed that you had not come yourself. 'I hardly 160 | expected,' he said, 'that so humble an individual as myself, 161 | especially after my heavy financial loss, could obtain the complete 162 | attention of so famous a man as Mr. Sherlock Holmes.' 163 | 164 | "I assured him that the financial question did not arise. 'No, of 165 | course, it is art for art's sake with him,' said he, 'but even on the 166 | artistic side of crime he might have found something here to study. 167 | And human nature, Dr. Watson--the black ingratitude of it all! When 168 | did I ever refuse one of her requests? Was ever a woman so pampered? 169 | And that young man--he might have been my own son. He had the run of 170 | my house. And yet see how they have treated me! Oh, Dr. Watson, it is 171 | a dreadful, dreadful world!' 172 | 173 | "That was the burden of his song for an hour or more. He had, it 174 | seems, no suspicion of an intrigue. They lived alone save for a woman 175 | who comes in by the day and leaves every evening at six. On that 176 | particular evening old Amberley, wishing to give his wife a treat, 177 | had taken two upper circle seats at the Haymarket Theatre. At the 178 | last moment she had complained of a headache and had refused to go. 179 | He had gone alone. There seemed to be no doubt about the fact, for he 180 | produced the unused ticket which he had taken for his wife." 181 | 182 | "That is remarkable--most remarkable," said Holmes, whose interest in 183 | the case seemed to be rising. "Pray continue, Watson. I find your 184 | narrative most arresting. Did you personally examine this ticket? You 185 | did not, perchance, take the number?" 186 | 187 | "It so happens that I did," I answered with some pride. "It chanced 188 | to be my old school number, thirty-one, and so is stuck in my head." 189 | 190 | "Excellent, Watson! His seat, then, was either thirty or thirty-two." 191 | 192 | "Quite so," I answered with some mystification. "And on B row." 193 | 194 | "That is most satisfactory. What else did he tell you?" 195 | 196 | "He showed me his strong-room, as he called it. It really is a 197 | strong-room--like a bank--with iron door and shutter--burglar-proof, 198 | as he claimed. However, the woman seems to have had a duplicate key, 199 | and between them they had carried off some seven thousand pounds' 200 | worth of cash and securities." 201 | 202 | "Securities! How could they dispose of those?" 203 | 204 | "He said that he had given the police a list and that he hoped they 205 | would be unsaleable. He had got back from the theatre about midnight 206 | and found the place plundered, the door and window open, and the 207 | fugitives gone. There was no letter or message, nor has he heard a 208 | word since. He at once gave the alarm to the police." 209 | 210 | Holmes brooded for some minutes. 211 | 212 | "You say he was painting. What was he painting?" 213 | 214 | "Well, he was painting the passage. But he had already painted the 215 | door and woodwork of this room I spoke of." 216 | 217 | "Does it not strike you as a strange occupation in the 218 | circumstances?" 219 | 220 | "'One must do something to ease an aching heart.' That was his own 221 | explanation. It was eccentric, no doubt, but he is clearly an 222 | eccentric man. He tore up one of his wife's photographs in my 223 | presence--tore it up furiously in a tempest of passion. 'I never wish 224 | to see her damned face again,' he shrieked." 225 | 226 | "Anything more, Watson?" 227 | 228 | "Yes, one thing which struck me more than anything else. I had driven 229 | to the Blackheath Station and had caught my train there when, just as 230 | it was starting, I saw a man dart into the carriage next to my own. 231 | You know that I have a quick eye for faces, Holmes. It was 232 | undoubtedly the tall, dark man whom I had addressed in the street. I 233 | saw him once more at London Bridge, and then I lost him in the crowd. 234 | But I am convinced that he was following me." 235 | 236 | "No doubt! No doubt!" said Holmes. "A tall, dark, heavily moustached 237 | man, you say, with gray-tinted sun-glasses?" 238 | 239 | "Holmes, you are a wizard. I did not say so, but he had gray-tinted 240 | sun-glasses." 241 | 242 | "And a Masonic tie-pin?" 243 | 244 | "Holmes!" 245 | 246 | "Quite simple, my dear Watson. But let us get down to what is 247 | practical. I must admit to you that the case, which seemed to me to 248 | be so absurdly simple as to be hardly worth my notice, is rapidly 249 | assuming a very different aspect. It is true that though in your 250 | mission you have missed everything of importance, yet even those 251 | things which have obtruded themselves upon your notice give rise to 252 | serious thought." 253 | 254 | "What have I missed?" 255 | 256 | "Don't be hurt, my dear fellow. You know that I am quite impersonal. 257 | No one else would have done better. Some possibly not so well. But 258 | clearly you have missed some vital points. What is the opinion of the 259 | neighbours about this man Amberley and his wife? That surely is of 260 | importance. What of Dr. Ernest? Was he the gay Lothario one would 261 | expect? With your natural advantages, Watson, every lady is your 262 | helper and accomplice. What about the girl at the post-office, or the 263 | wife of the greengrocer? I can picture you whispering soft nothings 264 | with the young lady at the Blue Anchor, and receiving hard somethings 265 | in exchange. All this you have left undone." 266 | 267 | "It can still be done." 268 | 269 | "It has been done. Thanks to the telephone and the help of the Yard, 270 | I can usually get my essentials without leaving this room. As a 271 | matter of fact, my information confirms the man's story. He has the 272 | local repute of being a miser as well as a harsh and exacting 273 | husband. That he had a large sum of money in that strong-room of his 274 | is certain. So also is it that young Dr. Ernest, an unmarried man, 275 | played chess with Amberley, and probably played the fool with his 276 | wife. All this seems plain sailing, and one would think that there 277 | was no more to be said--and yet!--and yet!" 278 | 279 | "Where lies the difficulty?" 280 | 281 | "In my imagination, perhaps. Well, leave it there, Watson. Let us 282 | escape from this weary workaday world by the side door of music. 283 | Carina sings to-night at the Albert Hall, and we still have time to 284 | dress, dine, and enjoy." 285 | 286 | In the morning I was up betimes, but some toast crumbs and two empty 287 | egg-shells told me that my companion was earlier still. I found a 288 | scribbled note upon the table. 289 | 290 | Dear Watson: 291 | There are one or two points of contact which I should wish to 292 | establish with Mr. Josiah Amberley. When I have done so we can 293 | dismiss the case--or not. I would only ask you to be on hand about 294 | three o'clock, as I conceive it possible that I may want you. 295 | S. H. 296 | 297 | I saw nothing of Holmes all day, but at the hour named he returned, 298 | grave, preoccupied, and aloof. At such times it was wiser to leave 299 | him to himself. 300 | 301 | "Has Amberley been here yet?" 302 | 303 | "No." 304 | 305 | "Ah! I am expecting him." 306 | 307 | He was not disappointed, for presently the old fellow arrived with a 308 | very worried and puzzled expression upon his austere face. 309 | 310 | "I've had a telegram, Mr. Holmes. I can make nothing of it." He 311 | handed it over, and Holmes read it aloud. 312 | 313 | "Come at once without fail. Can give you information as to your 314 | recent loss. 315 | "Elman. 316 | "The Vicarage. 317 | 318 | "Dispatched at 2.10 from Little Purlington," said Holmes. "Little 319 | Purlington is in Essex, I believe, not far from Frinton. Well, of 320 | course you will start at once. This is evidently from a responsible 321 | person, the vicar of the place. Where is my Crockford? Yes, here we 322 | have him: 'J. C. Elman, M. A., Living of Moosmoor cum Little 323 | Purlington.' Look up the trains, Watson." 324 | 325 | "There is one at 5.20 from Liverpool Street." 326 | 327 | "Excellent. You had best go with him, Watson. He may need help or 328 | advice. Clearly we have come to a crisis in this affair." 329 | 330 | But our client seemed by no means eager to start. 331 | 332 | "It's perfectly absurd, Mr. Holmes," he said. "What can this man 333 | possibly know of what has occurred? It is waste of time and money." 334 | 335 | "He would not have telegraphed to you if he did not know something. 336 | Wire at once that you are coming." 337 | 338 | "I don't think I shall go." 339 | 340 | Holmes assumed his sternest aspect. 341 | 342 | "It would make the worst possible impression both on the police and 343 | upon myself, Mr. Amberley, if when so obvious a clue arose you should 344 | refuse to follow it up. We should feel that you were not really in 345 | earnest in this investigation." 346 | 347 | Our client seemed horrified at the suggestion. 348 | 349 | "Why, of course I shall go if you look at it in that way," said he. 350 | "On the face of it, it seems absurd to suppose that this person knows 351 | anything, but if you think--" 352 | 353 | "I do think," said Holmes with emphasis, and so we were launched upon 354 | our journey. Holmes took me aside before we left the room and gave me 355 | one word of counsel, which showed that he considered the matter to be 356 | of importance. "Whatever you do, see that he really does go," said 357 | he. "Should he break away or return, get to the nearest telephone 358 | exchange and send the single word 'Bolted.' I will arrange here that 359 | it shall reach me wherever I am." 360 | 361 | Little Purlington is not an easy place to reach, for it is on a 362 | branch line. My remembrance of the journey is not a pleasant one, for 363 | the weather was hot, the train slow, and my companion sullen and 364 | silent, hardly talking at all save to make an occasional sardonic 365 | remark as to the futility of our proceedings. When we at last reached 366 | the little station it was a two-mile drive before we came to the 367 | Vicarage, where a big, solemn, rather pompous clergyman received us 368 | in his study. Our telegram lay before him. 369 | 370 | "Well, gentlemen," he asked, "what can I do for you?" 371 | 372 | "We came," I explained, "in answer to your wire." 373 | 374 | "My wire! I sent no wire." 375 | 376 | "I mean the wire which you sent to Mr. Josiah Amberley about his wife 377 | and his money." 378 | 379 | "If this is a joke, sir, it is a very questionable one," said the 380 | vicar angrily. "I have never heard of the gentleman you name, and I 381 | have not sent a wire to anyone." 382 | 383 | Our client and I looked at each other in amazement. 384 | 385 | "Perhaps there is some mistake," said I; "are there perhaps two 386 | vicarages? Here is the wire itself, signed Elman and dated from the 387 | Vicarage." 388 | 389 | "There is only one vicarage, sir, and only one vicar, and this wire 390 | is a scandalous forgery, the origin of which shall certainly be 391 | investigated by the police. Meanwhile, I can see no possible object 392 | in prolonging this interview." 393 | 394 | So Mr. Amberley and I found ourselves on the roadside in what seemed 395 | to me to be the most primitive village in England. We made for the 396 | telegraph office, but it was already closed. There was a telephone, 397 | however, at the little Railway Arms, and by it I got into touch with 398 | Holmes, who shared in our amazement at the result of our journey. 399 | 400 | "Most singular!" said the distant voice. "Most remarkable! I much 401 | fear, my dear Watson, that there is no return train to-night. I have 402 | unwittingly condemned you to the horrors of a country inn. However, 403 | there is always Nature, Watson--Nature and Josiah Amberley--you can 404 | be in close commune with both." I heard his dry chuckle as he turned 405 | away. 406 | 407 | It was soon apparent to me that my companion's reputation as a miser 408 | was not undeserved. He had grumbled at the expense of the journey, 409 | had insisted upon travelling third-class, and was now clamorous in 410 | his objections to the hotel bill. Next morning, when we did at last 411 | arrive in London, it was hard to say which of us was in the worse 412 | humour. 413 | 414 | "You had best take Baker Street as we pass," said I. "Mr. Holmes may 415 | have some fresh instructions." 416 | 417 | "If they are not worth more than the last ones they are not of much 418 | use, " said Amberley with a malevolent scowl. None the less, he kept 419 | me company. I had already warned Holmes by telegram of the hour of 420 | our arrival, but we found a message waiting that he was at Lewisham 421 | and would expect us there. That was a surprise, but an even greater 422 | one was to find that he was not alone in the sitting-room of our 423 | client. A stern-looking, impassive man sat beside him, a dark man 424 | with gray-tinted glasses and a large Masonic pin projecting from his 425 | tie. 426 | 427 | "This is my friend Mr. Barker," said Holmes. "He has been interesting 428 | himself also in your business, Mr. Josiah Amberley, though we have 429 | been working independently. But we both have the same question to ask 430 | you!" 431 | 432 | Mr. Amberley sat down heavily. He sensed impending danger. I read it 433 | in his straining eyes and his twitching features. 434 | 435 | "What is the question, Mr. Holmes?" 436 | 437 | "Only this: What did you do with the bodies?" 438 | 439 | The man sprang to his feet with a hoarse scream. He clawed into the 440 | air with his bony hands. His mouth was open, and for the instant he 441 | looked like some horrible bird of prey. In a flash we got a glimpse 442 | of the real Josiah Amberley, a misshapen demon with a soul as 443 | distorted as his body. As he fell back into his chair he clapped his 444 | hand to his lips as if to stifle a cough. Holmes sprang at his throat 445 | like a tiger and twisted his face towards the ground. A white pellet 446 | fell from between his gasping lips. 447 | 448 | "No short cuts, Josiah Amberley. Things must be done decently and in 449 | order. What about it, Barker?" 450 | 451 | "I have a cab at the door," said our taciturn companion. 452 | 453 | "It is only a few hundred yards to the station. We will go together. 454 | You can stay here, Watson. I shall be back within half an hour." 455 | 456 | The old colourman had the strength of a lion in that great trunk of 457 | his, but he was helpless in the hands of the two experienced 458 | man-handlers. Wriggling and twisting he was dragged to the waiting 459 | cab, and I was left to my solitary vigil in the ill-omened house. In 460 | less time than he had named, however, Holmes was back, in company 461 | with a smart young police inspector. 462 | 463 | "I've left Barker to look after the formalities," said Holmes. "You 464 | had not met Barker, Watson. He is my hated rival upon the Surrey 465 | shore. When you said a tall dark man it was not difficult for me to 466 | complete the picture. He has several good cases to his credit, has he 467 | not, Inspector?" 468 | 469 | "He has certainly interfered several times," the inspector answered 470 | with reserve. 471 | 472 | "His methods are irregular, no doubt, like my own. The irregulars are 473 | useful sometimes, you know. You, for example, with your compulsory 474 | warning about whatever he said being used against him, could never 475 | have bluffed this rascal into what is virtually a confession." 476 | 477 | "Perhaps not. But we get there all the same, Mr. Holmes. Don't 478 | imagine that we had not formed our own views of this case, and that 479 | we would not have laid our hands on our man. You will excuse us for 480 | feeling sore when you jump in with methods which we cannot use, and 481 | so rob us of the credit." 482 | 483 | "There shall be no such robbery, MacKinnon. I assure you that I 484 | efface myself from now onward, and as to Barker, he has done nothing 485 | save what I told him." 486 | 487 | The inspector seemed considerably relieved. 488 | 489 | "That is very handsome of you, Mr. Holmes. Praise or blame can matter 490 | little to you, but it is very different to us when the newspapers 491 | begin to ask questions." 492 | 493 | "Quite so. But they are pretty sure to ask questions anyhow, so it 494 | would be as well to have answers. What will you say, for example, 495 | when the intelligent and enterprising reporter asks you what the 496 | exact points were which aroused your suspicion, and finally gave you 497 | a certain conviction as to the real facts?" 498 | 499 | The inspector looked puzzled. 500 | 501 | "We don't seem to have got any real facts yet, Mr. Holmes. You say 502 | that the prisoner, in the presence of three witnesses, practically 503 | confessed by trying to commit suicide, that he had murdered his wife 504 | and her lover. What other facts have you?" 505 | 506 | "Have you arranged for a search?" 507 | 508 | "There are three constables on their way." 509 | 510 | "Then you will soon get the clearest fact of all. The bodies cannot 511 | be far away. Try the cellars and the garden. It should not take long 512 | to dig up the likely places. This house is older than the 513 | water-pipes. There must be a disused well somewhere. Try your luck 514 | there." 515 | 516 | "But how did you know of it, and how was it done?" 517 | 518 | "I'll show you first how it was done, and then I will give the 519 | explanation which is due to you, and even more to my long-suffering 520 | friend here, who has been invaluable throughout. But, first, I would 521 | give you an insight into this man's mentality. It is a very unusual 522 | one--so much so that I think his destination is more likely to be 523 | Broadmoor than the scaffold. He has, to a high degree, the sort of 524 | mind which one associates with the mediaeval Italian nature rather 525 | than with the modern Briton. He was a miserable miser who made his 526 | wife so wretched by his niggardly ways that she was a ready prey for 527 | any adventurer. Such a one came upon the scene in the person of this 528 | chess-playing doctor. Amberley excelled at chess--one mark, Watson, 529 | of a scheming mind. Like all misers, he was a jealous man, and his 530 | jealousy became a frantic mania. Rightly or wrongly, he suspected an 531 | intrigue. He determined to have his revenge, and he planned it with 532 | diabolical cleverness. Come here!" 533 | 534 | Holmes led us along the passage with as much certainty as if he had 535 | lived in the house and halted at the open door of the strong-room. 536 | 537 | "Pooh! What an awful smell of paint!" cried the inspector. 538 | 539 | "That was our first clue," said Holmes. "You can thank Dr. Watson's 540 | observation for that, though he failed to draw the inference. It set 541 | my foot upon the trail. Why should this man at such a time be filling 542 | his house with strong odours? Obviously, to cover some other smell 543 | which he wished to conceal--some guilty smell which would suggest 544 | suspicions. Then came the idea of a room such as you see here with 545 | iron door and shutter--a hermetically sealed room. Put those two 546 | facts together, and whither do they lead? I could only determine that 547 | by examining the house myself. I was already certain that the case 548 | was serious, for I had examined the box-office chart at the Haymarket 549 | Theatre--another of Dr. Watson's bull's-eyes--and ascertained that 550 | neither B thirty nor thirty-two of the upper circle had been occupied 551 | that night. Therefore, Amberley had not been to the theatre, and his 552 | alibi fell to the ground. He made a bad slip when he allowed my 553 | astute friend to notice the number of the seat taken for his wife. 554 | The question now arose how I might be able to examine the house. I 555 | sent an agent to the most impossible village I could think of, and 556 | summoned my man to it at such an hour that he could not possibly get 557 | back. To prevent any miscarriage, Dr. Watson accompanied him. The 558 | good vicar's name I took, of course, out of my Crockford. Do I make 559 | it all clear to you?" 560 | 561 | "It is masterly," said the inspector in an awed voice. 562 | 563 | "There being no fear of interruption I proceeded to burgle the house. 564 | Burglary has always been an alternative profession had I cared to 565 | adopt it, and I have little doubt that I should have come to the 566 | front. Observe what I found. You see the gas-pipe along the skirting 567 | here. Very good. It rises in the angle of the wall, and there is a 568 | tap here in the corner. The pipe runs out into the strong-room, as 569 | you can see, and ends in that plaster rose in the centre of the 570 | ceiling, where it is concealed by the ornamentation. That end is wide 571 | open. At any moment by turning the outside tap the room could be 572 | flooded with gas. With door and shutter closed and the tap full on I 573 | would not give two minutes of conscious sensation to anyone shut up 574 | in that little chamber. By what devilish device he decoyed them there 575 | I do not know, but once inside the door they were at his mercy." 576 | 577 | The inspector examined the pipe with interest. "One of our officers 578 | mentioned the smell of gas," said he, "but of course the window and 579 | door were open then, and the paint--or some of it--was already about. 580 | He had begun the work of painting the day before, according to his 581 | story. But what next, Mr. Holmes?" 582 | 583 | "Well, then came an incident which was rather unexpected to myself. I 584 | was slipping through the pantry window in the early dawn when I felt 585 | a hand inside my collar, and a voice said: 'Now, you rascal, what are 586 | you doing in there?' When I could twist my head round I looked into 587 | the tinted spectacles of my friend and rival, Mr. Barker. It was a 588 | curious foregathering and set us both smiling. It seems that he had 589 | been engaged by Dr. Ray Ernest's family to make some investigations 590 | and had come to the same conclusion as to foul play. He had watched 591 | the house for some days and had spotted Dr. Watson as one of the 592 | obviously suspicious characters who had called there. He could hardly 593 | arrest Watson, but when he saw a man actually climbing out of the 594 | pantry window there came a limit to his restraint. Of course, I told 595 | him how matters stood and we continued the case together." 596 | 597 | "Why him? Why not us?" 598 | 599 | "Because it was in my mind to put that little test which answered so 600 | admirably. I fear you would not have gone so far." 601 | 602 | The inspector smiled. 603 | 604 | "Well, maybe not. I understand that I have your word, Mr. Holmes, 605 | that you step right out of the case now and that you turn all your 606 | results over to us." 607 | 608 | "Certainly, that is always my custom." 609 | 610 | "Well, in the name of the force I thank you. It seems a clear case, 611 | as you put it, and there can't be much difficulty over the bodies." 612 | 613 | "I'll show you a grim little bit of evidence," said Holmes, "and I am 614 | sure Amberley himself never observed it. You'll get results, 615 | Inspector, by always putting yourself in the other fellow's place, 616 | and thinking what you would do yourself. It takes some imagination, 617 | but it pays. Now, we will suppose that you were shut up in this 618 | little room, had not two minutes to live, but wanted to get even with 619 | the fiend who was probably mocking at you from the other side of the 620 | door. What would you do?" 621 | 622 | "Write a message." 623 | 624 | "Exactly. You would like to tell people how you died. No use writing 625 | on paper. That would be seen. If you wrote on the wall someone might 626 | rest upon it. Now, look here! Just above the skirting is scribbled 627 | with a purple indelible pencil: 'We we--' That's all." 628 | 629 | "What do you make of that?" 630 | 631 | "Well, it's only a foot above the ground. The poor devil was on the 632 | floor dying when he wrote it. He lost his senses before he could 633 | finish." 634 | 635 | "He was writing, 'We were murdered.'" 636 | 637 | "That's how I read it. If you find an indelible pencil on the body--" 638 | 639 | "We'll look out for it, you may be sure. But those securities? 640 | Clearly there was no robbery at all. And yet he did possess those 641 | bonds. We verified that." 642 | 643 | "You may be sure he has them hidden in a safe place. When the whole 644 | elopement had passed into history, he would suddenly discover them 645 | and announce that the guilty couple had relented and sent back the 646 | plunder or had dropped it on the way." 647 | 648 | "You certainly seem to have met every difficulty," said the 649 | inspector. "Of course, he was bound to call us in, but why he should 650 | have gone to you I can't understand." 651 | 652 | "Pure swank!" Holmes answered. "He felt so clever and so sure of 653 | himself that he imagined no one could touch him. He could say to any 654 | suspicious neighbour, 'Look at the steps I have taken. I have 655 | consulted not only the police but even Sherlock Holmes.'" 656 | 657 | The inspector laughed. 658 | 659 | "We must forgive you your 'even,' Mr. Holmes," said he, "it's as 660 | workmanlike a job as I can remember." 661 | 662 | A couple of days later my friend tossed across to me a copy of the 663 | bi-weekly North Surrey Observer. Under a series of flaming headlines, 664 | which began with "The Haven Horror" and ended with "Brilliant Police 665 | Investigation," there was a packed column of print which gave the 666 | first consecutive account of the affair. The concluding paragraph is 667 | typical of the whole. It ran thus: 668 | 669 | The remarkable acumen by which Inspector MacKinnon deduced from the 670 | smell of paint that some other smell, that of gas, for example, might 671 | be concealed; the bold deduction that the strong-room might also be 672 | the death-chamber, and the subsequent inquiry which led to the 673 | discovery of the bodies in a disused well, cleverly concealed by a 674 | dog-kennel, should live in the history of crime as a standing example 675 | of the intelligence of our professional detectives. 676 | 677 | "Well, well, MacKinnon is a good fellow," said Holmes with a tolerant 678 | smile. "You can file it in our archives, Watson. Some day the true 679 | story may be told." 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | ---------- 690 | This text is provided to you "as-is" without any warranty. No 691 | warranties of any kind, expressed or implied, are made to you as to 692 | the text or any medium it may be on, including but not limited to 693 | warranties of merchantablity or fitness for a particular purpose. 694 | 695 | This text was formatted from various free ASCII and HTML variants. 696 | See http://sherlock-holm.es for an electronic form of this text and 697 | additional information about it. 698 | 699 | This text comes from the collection's version 3.1. 700 | 701 | 702 | 703 | 704 | 705 | 706 | -------------------------------------------------------------------------------- /packages/example/assets/dyin.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | THE ADVENTURE OF THE DYING DETECTIVE 6 | 7 | Arthur Conan Doyle 8 | 9 | 10 | 11 | Mrs. Hudson, the landlady of Sherlock Holmes, was a long-suffering 12 | woman. Not only was her first-floor flat invaded at all hours by 13 | throngs of singular and often undesirable characters but her 14 | remarkable lodger showed an eccentricity and irregularity in his life 15 | which must have sorely tried her patience. His incredible untidiness, 16 | his addiction to music at strange hours, his occasional revolver 17 | practice within doors, his weird and often malodorous scientific 18 | experiments, and the atmosphere of violence and danger which hung 19 | around him made him the very worst tenant in London. On the other 20 | hand, his payments were princely. I have no doubt that the house 21 | might have been purchased at the price which Holmes paid for his 22 | rooms during the years that I was with him. 23 | 24 | The landlady stood in the deepest awe of him and never dared to 25 | interfere with him, however outrageous his proceedings might seem. 26 | She was fond of him, too, for he had a remarkable gentleness and 27 | courtesy in his dealings with women. He disliked and distrusted the 28 | sex, but he was always a chivalrous opponent. Knowing how genuine was 29 | her regard for him, I listened earnestly to her story when she came 30 | to my rooms in the second year of my married life and told me of the 31 | sad condition to which my poor friend was reduced. 32 | 33 | "He's dying, Dr. Watson," said she. "For three days he has been 34 | sinking, and I doubt if he will last the day. He would not let me get 35 | a doctor. This morning when I saw his bones sticking out of his face 36 | and his great bright eyes looking at me I could stand no more of it. 37 | 'With your leave or without it, Mr. Holmes, I am going for a doctor 38 | this very hour,' said I. 'Let it be Watson, then,' said he. I 39 | wouldn't waste an hour in coming to him, sir, or you may not see him 40 | alive." 41 | 42 | I was horrified for I had heard nothing of his illness. I need not 43 | say that I rushed for my coat and my hat. As we drove back I asked 44 | for the details. 45 | 46 | "There is little I can tell you, sir. He has been working at a case 47 | down at Rotherhithe, in an alley near the river, and he has brought 48 | this illness back with him. He took to his bed on Wednesday afternoon 49 | and has never moved since. For these three days neither food nor 50 | drink has passed his lips." 51 | 52 | "Good God! Why did you not call in a doctor?" 53 | 54 | "He wouldn't have it, sir. You know how masterful he is. I didn't 55 | dare to disobey him. But he's not long for this world, as you'll see 56 | for yourself the moment that you set eyes on him." 57 | 58 | He was indeed a deplorable spectacle. In the dim light of a foggy 59 | November day the sick room was a gloomy spot, but it was that gaunt, 60 | wasted face staring at me from the bed which sent a chill to my 61 | heart. His eyes had the brightness of fever, there was a hectic flush 62 | upon either cheek, and dark crusts clung to his lips; the thin hands 63 | upon the coverlet twitched incessantly, his voice was croaking and 64 | spasmodic. He lay listlessly as I entered the room, but the sight of 65 | me brought a gleam of recognition to his eyes. 66 | 67 | "Well, Watson, we seem to have fallen upon evil days," said he in a 68 | feeble voice, but with something of his old carelessness of manner. 69 | 70 | "My dear fellow!" I cried, approaching him. 71 | 72 | "Stand back! Stand right back!" said he with the sharp imperiousness 73 | which I had associated only with moments of crisis. "If you approach 74 | me, Watson, I shall order you out of the house." 75 | 76 | "But why?" 77 | 78 | "Because it is my desire. Is that not enough?" 79 | 80 | Yes, Mrs. Hudson was right. He was more masterful than ever. It was 81 | pitiful, however, to see his exhaustion. 82 | 83 | "I only wished to help," I explained. 84 | 85 | "Exactly! You will help best by doing what you are told." 86 | 87 | "Certainly, Holmes." 88 | 89 | He relaxed the austerity of his manner. 90 | 91 | "You are not angry?" he asked, gasping for breath. 92 | 93 | Poor devil, how could I be angry when I saw him lying in such a 94 | plight before me? 95 | 96 | "It's for your own sake, Watson," he croaked. 97 | 98 | "For my sake?" 99 | 100 | "I know what is the matter with me. It is a coolie disease from 101 | Sumatra--a thing that the Dutch know more about than we, though they 102 | have made little of it up to date. One thing only is certain. It is 103 | infallibly deadly, and it is horribly contagious." 104 | 105 | He spoke now with a feverish energy, the long hands twitching and 106 | jerking as he motioned me away. 107 | 108 | "Contagious by touch, Watson--that's it, by touch. Keep your distance 109 | and all is well." 110 | 111 | "Good heavens, Holmes! Do you suppose that such a consideration 112 | weighs with me of an instant? It would not affect me in the case of a 113 | stranger. Do you imagine it would prevent me from doing my duty to so 114 | old a friend?" 115 | 116 | Again I advanced, but he repulsed me with a look of furious anger. 117 | 118 | "If you will stand there I will talk. If you do not you must leave 119 | the room." 120 | 121 | I have so deep a respect for the extraordinary qualities of Holmes 122 | that I have always deferred to his wishes, even when I least 123 | understood them. But now all my professional instincts were aroused. 124 | Let him be my master elsewhere, I at least was his in a sick room. 125 | 126 | "Holmes," said I, "you are not yourself. A sick man is but a child, 127 | and so I will treat you. Whether you like it or not, I will examine 128 | your symptoms and treat you for them." 129 | 130 | He looked at me with venomous eyes. 131 | 132 | "If I am to have a doctor whether I will or not, let me at least have 133 | someone in whom I have confidence," said he. 134 | 135 | "Then you have none in me?" 136 | 137 | "In your friendship, certainly. But facts are facts, Watson, and, 138 | after all, you are only a general practitioner with very limited 139 | experience and mediocre qualifications. It is painful to have to say 140 | these things, but you leave me no choice." 141 | 142 | I was bitterly hurt. 143 | 144 | "Such a remark is unworthy of you, Holmes. It shows me very clearly 145 | the state of your own nerves. But if you have no confidence in me I 146 | would not intrude my services. Let me bring Sir Jasper Meek or 147 | Penrose Fisher, or any of the best men in London. But someone you 148 | must have, and that is final. If you think that I am going to stand 149 | here and see you die without either helping you myself or bringing 150 | anyone else to help you, then you have mistaken your man." 151 | 152 | "You mean well, Watson," said the sick man with something between a 153 | sob and a groan. "Shall I demonstrate your own ignorance? What do you 154 | know, pray, of Tapanuli fever? What do you know of the black Formosa 155 | corruption?" 156 | 157 | "I have never heard of either." 158 | 159 | "There are many problems of disease, many strange pathological 160 | possibilities, in the East, Watson." He paused after each sentence to 161 | collect his failing strength. "I have learned so much during some 162 | recent researches which have a medico-criminal aspect. It was in the 163 | course of them that I contracted this complaint. You can do nothing." 164 | 165 | "Possibly not. But I happen to know that Dr. Ainstree, the greatest 166 | living authority upon tropical disease, is now in London. All 167 | remonstrance is useless, Holmes, I am going this instant to fetch 168 | him." I turned resolutely to the door. 169 | 170 | Never have I had such a shock! In an instant, with a tiger-spring, 171 | the dying man had intercepted me. I heard the sharp snap of a twisted 172 | key. The next moment he had staggered back to his bed, exhausted and 173 | panting after his one tremendous outflame of energy. 174 | 175 | "You won't take the key from be by force, Watson, I've got you, my 176 | friend. Here you are, and here you will stay until I will otherwise. 177 | But I'll humour you." (All this in little gasps, with terrible 178 | struggles for breath between.) "You've only my own good at heart. Of 179 | course I know that very well. You shall have your way, but give me 180 | time to get my strength. Not now, Watson, not now. It's four o'clock. 181 | At six you can go." 182 | 183 | "This is insanity, Holmes." 184 | 185 | "Only two hours, Watson. I promise you will go at six. Are you 186 | content to wait?" 187 | 188 | "I seem to have no choice." 189 | 190 | "None in the world, Watson. Thank you, I need no help in arranging 191 | the clothes. You will please keep your distance. Now, Watson, there 192 | is one other condition that I would make. You will seek help, not 193 | from the man you mention, but from the one that I choose." 194 | 195 | "By all means." 196 | 197 | "The first three sensible words that you have uttered since you 198 | entered this room, Watson. You will find some books over there. I am 199 | somewhat exhausted; I wonder how a battery feels when it pours 200 | electricity into a non-conductor? At six, Watson, we resume our 201 | conversation." 202 | 203 | But it was destined to be resumed long before that hour, and in 204 | circumstances which gave me a shock hardly second to that caused by 205 | his spring to the door. I had stood for some minutes looking at the 206 | silent figure in the bed. His face was almost covered by the clothes 207 | and he appeared to be asleep. Then, unable to settle down to reading, 208 | I walked slowly round the room, examining the pictures of celebrated 209 | criminals with which every wall was adorned. Finally, in my aimless 210 | perambulation, I came to the mantelpiece. A litter of pipes, 211 | tobacco-pouches, syringes, penknives, revolver-cartridges, and other 212 | debris was scattered over it. In the midst of these was a small black 213 | and white ivory box with a sliding lid. It was a neat little thing, 214 | and I had stretched out my hand to examine it more closely when-- 215 | 216 | It was a dreadful cry that he gave--a yell which might have been 217 | heard down the street. My skin went cold and my hair bristled at that 218 | horrible scream. As I turned I caught a glimpse of a convulsed face 219 | and frantic eyes. I stood paralyzed, with the little box in my hand. 220 | 221 | "Put it down! Down, this instant, Watson--this instant, I say!" His 222 | head sank back upon the pillow and he gave a deep sigh of relief as I 223 | replaced the box upon the mantelpiece. "I hate to have my things 224 | touched, Watson. You know that I hate it. You fidget me beyond 225 | endurance. You, a doctor--you are enough to drive a patient into an 226 | asylum. Sit down, man, and let me have my rest!" 227 | 228 | The incident left a most unpleasant impression upon my mind. The 229 | violent and causeless excitement, followed by this brutality of 230 | speech, so far removed from his usual suavity, showed me how deep was 231 | the disorganization of his mind. Of all ruins, that of a noble mind 232 | is the most deplorable. I sat in silent dejection until the 233 | stipulated time had passed. He seemed to have been watching the clock 234 | as well as I, for it was hardly six before he began to talk with the 235 | same feverish animation as before. 236 | 237 | "Now, Watson," said he. "Have you any change in your pocket?" 238 | 239 | "Yes." 240 | 241 | "Any silver?" 242 | 243 | "A good deal." 244 | 245 | "How many half-crowns?" 246 | 247 | "I have five." 248 | 249 | "Ah, too few! Too few! How very unfortunate, Watson! However, such as 250 | they are you can put them in your watchpocket. And all the rest of 251 | your money in your left trouser pocket. Thank you. It will balance 252 | you so much better like that." 253 | 254 | This was raving insanity. He shuddered, and again made a sound 255 | between a cough and a sob. 256 | 257 | "You will now light the gas, Watson, but you will be very careful 258 | that not for one instant shall it be more than half on. I implore you 259 | to be careful, Watson. Thank you, that is excellent. No, you need not 260 | draw the blind. Now you will have the kindness to place some letters 261 | and papers upon this table within my reach. Thank you. Now some of 262 | that litter from the mantelpiece. Excellent, Watson! There is a 263 | sugar-tongs there. Kindly raise that small ivory box with its 264 | assistance. Place it here among the papers. Good! You can now go and 265 | fetch Mr. Culverton Smith, of 13 Lower Burke Street." 266 | 267 | To tell the truth, my desire to fetch a doctor had somewhat weakened, 268 | for poor Holmes was so obviously delirious that it seemed dangerous 269 | to leave him. However, he was as eager now to consult the person 270 | named as he had been obstinate in refusing. 271 | 272 | "I never heard the name," said I. 273 | 274 | "Possibly not, my good Watson. It may surprise you to know that the 275 | man upon earth who is best versed in this disease is not a medical 276 | man, but a planter. Mr. Culverton Smith is a well-known resident of 277 | Sumatra, now visiting London. An outbreak of the disease upon his 278 | plantation, which was distant from medical aid, caused him to study 279 | it himself, with some rather far-reaching consequences. He is a very 280 | methodical person, and I did not desire you to start before six, 281 | because I was well aware that you would not find him in his study. If 282 | you could persuade him to come here and give us the benefit of his 283 | unique experience of this disease, the investigation of which has 284 | been his dearest hobby, I cannot doubt that he could help me." 285 | 286 | I gave Holmes's remarks as a consecutive whole and will not attempt 287 | to indicate how they were interrupted by gaspings for breath and 288 | those clutchings of his hands which indicated the pain from which he 289 | was suffering. His appearance had changed for the worse during the 290 | few hours that I had been with him. Those hectic spots were more 291 | pronounced, the eyes shone more brightly out of darker hollows, and a 292 | cold sweat glimmered upon his brow. He still retained, however, the 293 | jaunty gallantry of his speech. To the last gasp he would always be 294 | the master. 295 | 296 | "You will tell him exactly how you have left me," said he. "You will 297 | convey the very impression which is in your own mind--a dying man--a 298 | dying and delirious man. Indeed, I cannot think why the whole bed of 299 | the ocean is not one solid mass of oysters, so prolific the creatures 300 | seem. Ah, I am wondering! Strange how the brain controls the brain! 301 | What was I saying, Watson?" 302 | 303 | "My directions for Mr. Culverton Smith." 304 | 305 | "Ah, yes, I remember. My life depends upon it. Plead with him, 306 | Watson. There is no good feeling between us. His nephew, Watson--I 307 | had suspicions of foul play and I allowed him to see it. The boy died 308 | horribly. He has a grudge against me. You will soften him, Watson. 309 | Beg him, pray him, get him here by any means. He can save me--only 310 | he!" 311 | 312 | "I will bring him in a cab, if I have to carry him down to it." 313 | 314 | "You will do nothing of the sort. You will persuade him to come. And 315 | then you will return in front of him. Make any excuse so as not to 316 | come with him. Don't forget, Watson. You won't fail me. You never did 317 | fail me. No doubt there are natural enemies which limit the increase 318 | of the creatures. You and I, Watson, we have done our part. Shall the 319 | world, then, be overrun by oysters? No, no; horrible! You'll convey 320 | all that is in your mind." 321 | 322 | I left him full of the image of this magnificent intellect babbling 323 | like a foolish child. He had handed me the key, and with a happy 324 | thought I took it with me lest he should lock himself in. Mrs. Hudson 325 | was waiting, trembling and weeping, in the passage. Behind me as I 326 | passed from the flat I heard Holmes's high, thin voice in some 327 | delirious chant. Below, as I stood whistling for a cab, a man came on 328 | me through the fog. 329 | 330 | "How is Mr. Holmes, sir?" he asked. 331 | 332 | It was an old acquaintance, Inspector Morton, of Scotland Yard, 333 | dressed in unofficial tweeds. 334 | 335 | "He is very ill," I answered. 336 | 337 | He looked at me in a most singular fashion. Had it not been too 338 | fiendish, I could have imagined that the gleam of the fanlight showed 339 | exultation in his face. 340 | 341 | "I heard some rumour of it," said he. 342 | 343 | The cab had driven up, and I left him. 344 | 345 | Lower Burke Street proved to be a line of fine houses lying in the 346 | vague borderland between Notting Hill and Kensington. The particular 347 | one at which my cabman pulled up had an air of smug and demure 348 | respectability in its old-fashioned iron railings, its massive 349 | folding-door, and its shining brasswork. All was in keeping with a 350 | solemn butler who appeared framed in the pink radiance of a tinted 351 | electrical light behind him. 352 | 353 | "Yes, Mr. Culverton Smith is in. Dr. Watson! Very good, sir, I will 354 | take up your card." 355 | 356 | My humble name and title did not appear to impress Mr. Culverton 357 | Smith. Through the half-open door I heard a high, petulant, 358 | penetrating voice. 359 | 360 | "Who is this person? What does he want? Dear me, Staples, how often 361 | have I said that I am not to be disturbed in my hours of study?" 362 | 363 | There came a gentle flow of soothing explanation from the butler. 364 | 365 | "Well, I won't see him, Staples. I can't have my work interrupted 366 | like this. I am not at home. Say so. Tell him to come in the morning 367 | if he really must see me." 368 | 369 | Again the gentle murmur. 370 | 371 | "Well, well, give him that message. He can come in the morning, or he 372 | can stay away. My work must not be hindered." 373 | 374 | I thought of Holmes tossing upon his bed of sickness and counting the 375 | minutes, perhaps, until I could bring help to him. It was not a time 376 | to stand upon ceremony. His life depended upon my promptness. Before 377 | the apologetic butler had delivered his message I had pushed past him 378 | and was in the room. 379 | 380 | With a shrill cry of anger a man rose from a reclining chair beside 381 | the fire. I saw a great yellow face, coarse-grained and greasy, with 382 | heavy, double-chin, and two sullen, menacing gray eyes which glared 383 | at me from under tufted and sandy brows. A high bald head had a small 384 | velvet smoking-cap poised coquettishly upon one side of its pink 385 | curve. The skull was of enormous capacity, and yet as I looked down I 386 | saw to my amazement that the figure of the man was small and frail, 387 | twisted in the shoulders and back like one who has suffered from 388 | rickets in his childhood. 389 | 390 | "What's this?" he cried in a high, screaming voice. "What is the 391 | meaning of this intrusion? Didn't I send you word that I would see 392 | you to-morrow morning?" 393 | 394 | "I am sorry," said I, "but the matter cannot be delayed. Mr. Sherlock 395 | Holmes--" 396 | 397 | The mention of my friend's name had an extraordinary effect upon the 398 | little man. The look of anger passed in an instant from his face. His 399 | features became tense and alert. 400 | 401 | "Have you come from Holmes?" he asked. 402 | 403 | "I have just left him." 404 | 405 | "What about Holmes? How is he?" 406 | 407 | "He is desperately ill. That is why I have come." 408 | 409 | The man motioned me to a chair, and turned to resume his own. As he 410 | did so I caught a glimpse of his face in the mirror over the 411 | mantelpiece. I could have sworn that it was set in a malicious and 412 | abominable smile. Yet I persuaded myself that it must have been some 413 | nervous contraction which I had surprised, for he turned to me an 414 | instant later with genuine concern upon his features. 415 | 416 | "I am sorry to hear this," said he. "I only know Mr. Holmes through 417 | some business dealings which we have had, but I have every respect 418 | for his talents and his character. He is an amateur of crime, as I am 419 | of disease. For him the villain, for me the microbe. There are my 420 | prisons," he continued, pointing to a row of bottles and jars which 421 | stood upon a side table. "Among those gelatine cultivations some of 422 | the very worst offenders in the world are now doing time." 423 | 424 | "It was on account of your special knowledge that Mr. Holmes desired 425 | to see you. He has a high opinion of you and thought that you were 426 | the one man in London who could help him." 427 | 428 | The little man started, and the jaunty smoking-cap slid to the floor. 429 | 430 | "Why?" he asked. "Why should Mr. Homes think that I could help him in 431 | his trouble?" 432 | 433 | "Because of your knowledge of Eastern diseases." 434 | 435 | "But why should he think that this disease which he has contracted is 436 | Eastern?" 437 | 438 | "Because, in some professional inquiry, he has been working among 439 | Chinese sailors down in the docks." 440 | 441 | Mr. Culverton Smith smiled pleasantly and picked up his smoking-cap. 442 | 443 | "Oh, that's it--is it?" said he. "I trust the matter is not so grave 444 | as you suppose. How long has he been ill?" 445 | 446 | "About three days." 447 | 448 | "Is he delirious?" 449 | 450 | "Occasionally." 451 | 452 | "Tut, tut! This sounds serious. It would be inhuman not to answer his 453 | call. I very much resent any interruption to my work, Dr. Watson, but 454 | this case is certainly exceptional. I will come with you at once." 455 | 456 | I remembered Holmes's injunction. 457 | 458 | "I have another appointment," said I. 459 | 460 | "Very good. I will go alone. I have a note of Mr. Holmes's address. 461 | You can rely upon my being there within half an hour at most." 462 | 463 | It was with a sinking heart that I reentered Holmes's bedroom. For 464 | all that I knew the worst might have happened in my absence. To my 465 | enormous relief, he had improved greatly in the interval. His 466 | appearance was as ghastly as ever, but all trace of delirium had left 467 | him and he spoke in a feeble voice, it is true, but with even more 468 | than his usual crispness and lucidity. 469 | 470 | "Well, did you see him, Watson?" 471 | 472 | "Yes; he is coming." 473 | 474 | "Admirable, Watson! Admirable! You are the best of messengers." 475 | 476 | "He wished to return with me." 477 | 478 | "That would never do, Watson. That would be obviously impossible. Did 479 | he ask what ailed me?" 480 | 481 | "I told him about the Chinese in the East End." 482 | 483 | "Exactly! Well, Watson, you have done all that a good friend could. 484 | You can now disappear from the scene." 485 | 486 | "I must wait and hear his opinion, Holmes." 487 | 488 | "Of course you must. But I have reasons to suppose that this opinion 489 | would be very much more frank and valuable if he imagines that we are 490 | alone. There is just room behind the head of my bed, Watson." 491 | 492 | "My dear Holmes!" 493 | 494 | "I fear there is no alternative, Watson. The room does not lend 495 | itself to concealment, which is as well, as it is the less likely to 496 | arouse suspicion. But just there, Watson, I fancy that it could be 497 | done." Suddenly he sat up with a rigid intentness upon his haggard 498 | face. "There are the wheels, Watson. Quick, man, if you love me! And 499 | don't budge, whatever happens--whatever happens, do you hear? Don't 500 | speak! Don't move! Just listen with all your ears." Then in an 501 | instant his sudden access of strength departed, and his masterful, 502 | purposeful talk droned away into the low, vague murmurings of a 503 | semi-delirious man. 504 | 505 | From the hiding-place into which I had been so swiftly hustled I 506 | heard the footfalls upon the stair, with the opening and the closing 507 | of the bedroom door. Then, to my surprise, there came a long silence, 508 | broken only by the heavy breathings and gaspings of the sick man. I 509 | could imagine that our visitor was standing by the bedside and 510 | looking down at the sufferer. At last that strange hush was broken. 511 | 512 | "Holmes!" he cried. "Holmes!" in the insistent tone of one who 513 | awakens a sleeper. "Can't you hear me, Holmes?" There was a rustling, 514 | as if he had shaken the sick man roughly by the shoulder. 515 | 516 | "Is that you, Mr. Smith?" Holmes whispered. "I hardly dared hope that 517 | you would come." 518 | 519 | The other laughed. 520 | 521 | "I should imagine not," he said. "And yet, you see, I am here. Coals 522 | of fire, Holmes--coals of fire!" 523 | 524 | "It is very good of you--very noble of you. I appreciate your special 525 | knowledge." 526 | 527 | Our visitor sniggered. 528 | 529 | "You do. You are, fortunately, the only man in London who does. Do 530 | you know what is the matter with you?" 531 | 532 | "The same," said Holmes. 533 | 534 | "Ah! You recognize the symptoms?" 535 | 536 | "Only too well." 537 | 538 | "Well, I shouldn't be surprised, Holmes. I shouldn't be surprised if 539 | it were the same. A bad lookout for you if it is. Poor Victor was a 540 | dead man on the fourth day--a strong, hearty young fellow. It was 541 | certainly, as you said, very surprising that he should have 542 | contracted and out-of-the-way Asiatic disease in the heart of 543 | London--a disease, too, of which I had made such a very special 544 | study. Singular coincidence, Holmes. Very smart of you to notice it, 545 | but rather uncharitable to suggest that it was cause and effect." 546 | 547 | "I knew that you did it." 548 | 549 | "Oh, you did, did you? Well, you couldn't prove it, anyhow. But what 550 | do you think of yourself spreading reports about me like that, and 551 | then crawling to me for help the moment you are in trouble? What sort 552 | of a game is that--eh?" 553 | 554 | I heard the rasping, laboured breathing of the sick man. "Give me the 555 | water!" he gasped. 556 | 557 | "You're precious near your end, my friend, but I don't want you to go 558 | till I have had a word with you. That's why I give you water. There, 559 | don't slop it about! That's right. Can you understand what I say?" 560 | 561 | Holmes groaned. 562 | 563 | "Do what you can for me. Let bygones be bygones," he whispered. "I'll 564 | put the words out of my head--I swear I will. Only cure me, and I'll 565 | forget it." 566 | 567 | "Forget what?" 568 | 569 | "Well, about Victor Savage's death. You as good as admitted just now 570 | that you had done it. I'll forget it." 571 | 572 | "You can forget it or remember it, just as you like. I don't see you 573 | in the witnessbox. Quite another shaped box, my good Holmes, I assure 574 | you. It matters nothing to me that you should know how my nephew 575 | died. It's not him we are talking about. It's you." 576 | 577 | "Yes, yes." 578 | 579 | "The fellow who came for me--I've forgotten his name--said that you 580 | contracted it down in the East End among the sailors." 581 | 582 | "I could only account for it so." 583 | 584 | "You are proud of your brains, Holmes, are you not? Think yourself 585 | smart, don't you? You came across someone who was smarter this time. 586 | Now cast your mind back, Holmes. Can you think of no other way you 587 | could have got this thing?" 588 | 589 | "I can't think. My mind is gone. For heaven's sake help me!" 590 | 591 | "Yes, I will help you. I'll help you to understand just where you are 592 | and how you got there. I'd like you to know before you die." 593 | 594 | "Give me something to ease my pain." 595 | 596 | "Painful, is it? Yes, the coolies used to do some squealing towards 597 | the end. Takes you as cramp, I fancy." 598 | 599 | "Yes, yes; it is cramp." 600 | 601 | "Well, you can hear what I say, anyhow. Listen now! Can you remember 602 | any unusual incident in your life just about the time your symptoms 603 | began?" 604 | 605 | "No, no; nothing." 606 | 607 | "Think again." 608 | 609 | "I'm too ill to think." 610 | 611 | "Well, then, I'll help you. Did anything come by post?" 612 | 613 | "By post?" 614 | 615 | "A box by chance?" 616 | 617 | "I'm fainting--I'm gone!" 618 | 619 | "Listen, Holmes!" There was a sound as if he was shaking the dying 620 | man, and it was all that I could do to hold myself quiet in my 621 | hiding-place. "You must hear me. You shall hear me. Do you remember a 622 | box--an ivory box? It came on Wednesday. You opened it--do you 623 | remember?" 624 | 625 | "Yes, yes, I opened it. There was a sharp spring inside it. Some 626 | joke--" 627 | 628 | "It was no joke, as you will find to your cost. You fool, you would 629 | have it and you have got it. Who asked you to cross my path? If you 630 | had left me alone I would not have hurt you." 631 | 632 | "I remember," Holmes gasped. "The spring! It drew blood. This 633 | box--this on the table." 634 | 635 | "The very one, by George! And it may as well leave the room in my 636 | pocket. There goes your last shred of evidence. But you have the 637 | truth now, Holmes, and you can die with the knowledge that I killed 638 | you. You knew too much of the fate of Victor Savage, so I have sent 639 | you to share it. You are very near your end, Holmes. I will sit here 640 | and I will watch you die." 641 | 642 | Holmes's voice had sunk to an almost inaudible whisper. 643 | 644 | "What is that?" said Smith. "Turn up the gas? Ah, the shadows begin 645 | to fall, do they? Yes, I will turn it up, that I may see you the 646 | better." He crossed the room and the light suddenly brightened. "Is 647 | there any other little service that I can do you, my friend?" 648 | 649 | "A match and a cigarette." 650 | 651 | I nearly called out in my joy and my amazement. He was speaking in 652 | his natural voice--a little weak, perhaps, but the very voice I knew. 653 | There was a long pause, and I felt that Culverton Smith was standing 654 | in silent amazement looking down at his companion. 655 | 656 | "What's the meaning of this?" I heard him say at last in a dry, 657 | rasping tone. 658 | 659 | "The best way of successfully acting a part is to be it," said 660 | Holmes. "I give you my word that for three days I have tasted neither 661 | food nor drink until you were good enough to pour me out that glass 662 | of water. But it is the tobacco which I find most irksome. Ah, here 663 | are some cigarettes." I heard the striking of a match. "That is very 664 | much better. Halloa! halloa! Do I hear the step of a friend?" 665 | 666 | There were footfalls outside, the door opened, and Inspector Morton 667 | appeared. 668 | 669 | "All is in order and this is your man," said Holmes. 670 | 671 | The officer gave the usual cautions. 672 | 673 | "I arrest you on the charge of the murder of one Victor Savage," he 674 | concluded. 675 | 676 | "And you might add of the attempted murder of one Sherlock Holmes," 677 | remarked my friend with a chuckle. "To save an invalid trouble, 678 | Inspector, Mr. Culverton Smith was good enough to give our signal by 679 | turning up the gas. By the way, the prisoner has a small box in the 680 | right-hand pocket of his coat which it would be as well to remove. 681 | Thank you. I would handle it gingerly if I were you. Put it down 682 | here. It may play its part in the trial." 683 | 684 | There was a sudden rush and a scuffle, followed by the clash of iron 685 | and a cry of pain. 686 | 687 | "You'll only get yourself hurt," said the inspector. "Stand still, 688 | will you?" There was the click of the closing handcuffs. 689 | 690 | "A nice trap!" cried the high, snarling voice. "It will bring you 691 | into the dock, Holmes, not me. He asked me to come here to cure him. 692 | I was sorry for him and I came. Now he will pretend, no doubt, that I 693 | have said anything which he may invent which will corroborate his 694 | insane suspicions. You can lie as you like, Holmes. My word is 695 | always as good as yours." 696 | 697 | "Good heavens!" cried Holmes. "I had totally forgotten him. My dear 698 | Watson, I owe you a thousand apologies. To think that I should have 699 | overlooked you! I need not introduce you to Mr. Culverton Smith, 700 | since I understand that you met somewhat earlier in the evening. Have 701 | you the cab below? I will follow you when I am dressed, for I may be 702 | of some use at the station. 703 | 704 | "I never needed it more," said Holmes as he refreshed himself with a 705 | glass of claret and some biscuits in the intervals of his toilet. 706 | "However, as you know, my habits are irregular, and such a feat means 707 | less to me than to most men. It was very essential that I should 708 | impress Mrs. Hudson with the reality of my condition, since she was 709 | to convey it to you, and you in turn to him. You won't be offended, 710 | Watson? You will realize that among your many talents dissimulation 711 | finds no place, and that if you had shared my secret you would never 712 | have been able to impress Smith with the urgent necessity of his 713 | presence, which was the vital point of the whole scheme. Knowing his 714 | vindictive nature, I was perfectly certain that he would come to look 715 | upon his handiwork." 716 | 717 | "But your appearance, Holmes--your ghastly face?" 718 | 719 | "Three days of absolute fast does not improve one's beauty, Watson. 720 | For the rest, there is nothing which a sponge may not cure. With 721 | vaseline upon one's forehead, belladonna in one's eyes, rouge over 722 | the cheek-bones, and crusts of beeswax round one's lips, a very 723 | satisfying effect can be produced. Malingering is a subject upon 724 | which I have sometimes thought of writing a monograph. A little 725 | occasional talk about half-crowns, oysters, or any other extraneous 726 | subject produces a pleasing effect of delirium." 727 | 728 | "But why would you not let me near you, since there was in truth no 729 | infection?" 730 | 731 | "Can you ask, my dear Watson? Do you imagine that I have no respect 732 | for your medical talents? Could I fancy that your astute judgment 733 | would pass a dying man who, however weak, had no rise of pulse or 734 | temperature? At four yards, I could deceive you. If I failed to do 735 | so, who would bring my Smith within my grasp? No, Watson, I would not 736 | touch that box. You can just see if you look at it sideways where the 737 | sharp spring like a viper's tooth emerges as you open it. I dare say 738 | it was by some such device that poor Savage, who stood between this 739 | monster and a reversion, was done to death. My correspondence, 740 | however, is, as you know, a varied one, and I am somewhat upon my 741 | guard against any packages which reach me. It was clear to me, 742 | however, that by pretending that he had really succeeded in his 743 | design I might surprise a confession. That pretence I have carried 744 | out with the thoroughness of the true artist. Thank you, Watson, you 745 | must help me on with my coat. When we have finished at the 746 | police-station I think that something nutritious at Simpson's would 747 | not be out of place." 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | ---------- 758 | This text is provided to you "as-is" without any warranty. No 759 | warranties of any kind, expressed or implied, are made to you as to 760 | the text or any medium it may be on, including but not limited to 761 | warranties of merchantablity or fitness for a particular purpose. 762 | 763 | This text was formatted from various free ASCII and HTML variants. 764 | See http://sherlock-holm.es for an electronic form of this text and 765 | additional information about it. 766 | 767 | This text comes from the collection's version 3.1. 768 | 769 | 770 | 771 | 772 | 773 | 774 | -------------------------------------------------------------------------------- /packages/example/assets/maza.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | THE ADVENTURE OF THE MAZARIN STONE 6 | 7 | Arthur Conan Doyle 8 | 9 | 10 | 11 | It was pleasant to Dr. Watson to find himself once more in the untidy 12 | room of the first floor in Baker Street which had been the 13 | starting-point of so many remarkable adventures. He looked round him 14 | at the scientific charts upon the wall, the acid-charred bench of 15 | chemicals, the violin-case leaning in the corner, the coal-scuttle, 16 | which contained of old the pipes and tobacco. Finally, his eyes came 17 | round to the fresh and smiling face of Billy, the young but very wise 18 | and tactful page, who had helped a little to fill up the gap of 19 | loneliness and isolation which surrounded the saturnine figure of the 20 | great detective. 21 | 22 | "It all seems very unchanged, Billy. You don't change, either. I hope 23 | the same can be said of him?" 24 | 25 | Billy glanced with some solicitude at the closed door of the bedroom. 26 | 27 | "I think he's in bed and asleep," he said. 28 | 29 | It was seven in the evening of a lovely summer's day, but Dr. Watson 30 | was sufficiently familiar with the irregularity of his old friend's 31 | hours to feel no surprise at the idea. 32 | 33 | "That means a case, I suppose?" 34 | 35 | "Yes, sir, he is very hard at it just now. I'm frightened for his 36 | health. He gets paler and thinner, and he eats nothing. 'When will 37 | you be pleased to dine, Mr. Holmes?' Mrs. Hudson asked. 38 | 'Seven-thirty, the day after to-morrow,' said he. You know his way 39 | when he is keen on a case." 40 | 41 | "Yes, Billy, I know." 42 | 43 | "He's following someone. Yesterday he was out as a workman looking 44 | for a job. To-day he was an old woman. Fairly took me in, he did, and 45 | I ought to know his ways by now." Billy pointed with a grin to a very 46 | baggy parasol which leaned against the sofa. "That's part of the old 47 | woman's outfit," he said. 48 | 49 | "But what is it all about, Billy?" 50 | 51 | Billy sank his voice, as one who discusses great secrets of State. "I 52 | don't mind telling you, sir, but it should go no farther. It's this 53 | case of the Crown diamond." 54 | 55 | "What--the hundred-thousand-pound burglary?" 56 | 57 | "Yes, sir. They must get it back, sir. Why, we had the Prime Minister 58 | and the Home Secretary both sitting on that very sofa. Mr. Holmes was 59 | very nice to them. He soon put them at their ease and promised he 60 | would do all he could. Then there is Lord Cantlemere--" 61 | 62 | "Ah!" 63 | 64 | "Yes, sir, you know what that means. He's a stiff 'un, sir, if I may 65 | say so. I can get along with the Prime Minister, and I've nothing 66 | against the Home Secretary, who seemed a civil, obliging sort of man, 67 | but I can't stand his Lordship. Neither can Mr. Holmes, sir. You see, 68 | he don't believe in Mr. Holmes and he was against employing him. He'd 69 | rather he failed." 70 | 71 | "And Mr. Holmes knows it?" 72 | 73 | "Mr. Holmes always knows whatever there is to know." 74 | 75 | "Well, we'll hope he won't fail and that Lord Cantlemere will be 76 | confounded. But I say, Billy, what is that curtain for across the 77 | window?" 78 | 79 | "Mr. Holmes had it put up there three days ago. We've got something 80 | funny behind it." 81 | 82 | Billy advanced and drew away the drapery which screened the alcove of 83 | the bow window. 84 | 85 | Dr. Watson could not restrain a cry of amazement. There was a 86 | facsimile of his old friend, dressing-gown and all, the face turned 87 | three-quarters towards the window and downward, as though reading an 88 | invisible book, while the body was sunk deep in an armchair. Billy 89 | detached the head and held it in the air. 90 | 91 | "We put it at different angles, so that it may seem more lifelike. I 92 | wouldn't dare touch it if the blind were not down. But when it's up 93 | you can see this from across the way." 94 | 95 | "We used something of the sort once before." 96 | 97 | "Before my time," said Billy. He drew the window curtains apart and 98 | looked out into the street. "There are folk who watch us from over 99 | yonder. I can see a fellow now at the window. Have a look for 100 | yourself." 101 | 102 | Watson had taken a step forward when the bedroom door opened, and the 103 | long, thin form of Holmes emerged, his face pale and drawn, but his 104 | step and bearing as active as ever. With a single spring he was at 105 | the window, and had drawn the blind once more. 106 | 107 | "That will do, Billy," said he. "You were in danger of your life 108 | then, my boy, and I can't do without you just yet. Well, Watson, it 109 | is good to see you in your old quarters once again. You come at a 110 | critical moment." 111 | 112 | "So I gather." 113 | 114 | "You can go, Billy. That boy is a problem, Watson. How far am I 115 | justified in allowing him to be in danger?" 116 | 117 | "Danger of what, Holmes?" 118 | 119 | "Of sudden death. I'm expecting something this evening." 120 | 121 | "Expecting what?" 122 | 123 | "To be murdered, Watson." 124 | 125 | "No, no, you are joking, Holmes!" 126 | 127 | "Even my limited sense of humour could evolve a better joke than 128 | that. But we may be comfortable in the meantime, may we not? Is 129 | alcohol permitted? The gasogene and cigars are in the old place. Let 130 | me see you once more in the customary armchair. You have not, I hope, 131 | learned to despise my pipe and my lamentable tobacco? It has to take 132 | the place of food these days." 133 | 134 | "But why not eat?" 135 | 136 | "Because the faculties become refined when you starve them. Why, 137 | surely, as a doctor, my dear Watson, you must admit that what your 138 | digestion gains in the way of blood supply is so much lost to the 139 | brain. I am a brain, Watson. The rest of me is a mere appendix. 140 | Therefore, it is the brain I must consider." 141 | 142 | "But this danger, Holmes?" 143 | 144 | "Ah, yes, in case it should come off, it would perhaps be as well 145 | that you should burden your memory with the name and address of the 146 | murderer. You can give it to Scotland Yard, with my love and a 147 | parting blessing. Sylvius is the name--Count Negretto Sylvius. Write 148 | it down, man, write it down! 136 Moorside Gardens, N. W. Got it?" 149 | 150 | Watson's honest face was twitching with anxiety. He knew only too 151 | well the immense risks taken by Holmes and was well aware that what 152 | he said was more likely to be under-statement than exaggeration. 153 | Watson was always the man of action, and he rose to the occasion. 154 | 155 | "Count me in, Holmes. I have nothing to do for a day or two." 156 | 157 | "Your morals don't improve, Watson. You have added fibbing to your 158 | other vices. You bear every sign of the busy medical man, with calls 159 | on him every hour." 160 | 161 | "Not such important ones. But can't you have this fellow arrested?" 162 | 163 | "Yes, Watson, I could. That's what worries him so." 164 | 165 | "But why don't you?" 166 | 167 | "Because I don't know where the diamond is." 168 | 169 | "Ah! Billy told me--the missing Crown jewel!" 170 | 171 | "Yes, the great yellow Mazarin stone. I've cast my net and I have my 172 | fish. But I have not got the stone. What is the use of taking them? 173 | We can make the world a better place by laying them by the heels. But 174 | that is not what I am out for. It's the stone I want." 175 | 176 | "And is this Count Sylvius one of your fish?" 177 | 178 | "Yes, and he's a shark. He bites. The other is Sam Merton, the boxer. 179 | Not a bad fellow, Sam, but the Count has used him. Sam's not a shark. 180 | He is a great big silly bull-headed gudgeon. But he is flopping about 181 | in my net all the same." 182 | 183 | "Where is this Count Sylvius?" 184 | 185 | "I've been at his very elbow all the morning. You've seen me as an 186 | old lady, Watson. I was never more convincing. He actually picked up 187 | my parasol for me once. 'By your leave, madame,' said 188 | he--half-Italian, you know, and with the Southern graces of manner 189 | when in the mood, but a devil incarnate in the other mood. Life is 190 | full of whimsical happenings, Watson." 191 | 192 | "It might have been tragedy." 193 | 194 | "Well, perhaps it might. I followed him to old Straubenzee's workshop 195 | in the Minories. Straubenzee made the air-gun--a very pretty bit of 196 | work, as I understand, and I rather fancy it is in the opposite 197 | window at the present moment. Have you seen the dummy? Of course, 198 | Billy showed it to you. Well, it may get a bullet through its 199 | beautiful head at any moment. Ah, Billy, what is it?" 200 | 201 | The boy had reappeared in the room with a card upon a tray. Holmes 202 | glanced at it with raised eyebrows and an amused smile. 203 | 204 | "The man himself. I had hardly expected this. Grasp the nettle, 205 | Watson! A man of nerve. Possibly you have heard of his reputation as 206 | a shooter of big game. It would indeed be a triumphant ending to his 207 | excellent sporting record if he added me to his bag. This is a proof 208 | that he feels my toe very close behind his heel." 209 | 210 | "Send for the police." 211 | 212 | "I probably shall. But not just yet. Would you glance carefully out 213 | of the window, Watson, and see if anyone is hanging about in the 214 | street?" 215 | 216 | Watson looked warily round the edge of the curtain. 217 | 218 | "Yes, there is one rough fellow near the door." 219 | 220 | "That will be Sam Merton--the faithful but rather fatuous Sam. Where 221 | is this gentleman, Billy?" 222 | 223 | "In the waiting-room, sir." 224 | 225 | "Show him up when I ring." 226 | 227 | "Yes, sir." 228 | 229 | "If I am not in the room, show him in all the same." 230 | 231 | "Yes, sir." 232 | 233 | Watson waited until the door was closed, and then he turned earnestly 234 | to his companion. 235 | 236 | "Look here, Holmes, this is simply impossible. This is a desperate 237 | man, who sticks at nothing. He may have come to murder you." 238 | 239 | "I should not be surprised." 240 | 241 | "I insist upon staying with you." 242 | 243 | "You would be horribly in the way." 244 | 245 | "In his way?" 246 | 247 | "No, my dear fellow--in my way." 248 | 249 | "Well, I can't possibly leave you." 250 | 251 | "Yes, you can, Watson. And you will, for you have never failed to 252 | play the game. I am sure you will play it to the end. This man has 253 | come for his own purpose, but he may stay for mine." Holmes took out 254 | his notebook and scribbled a few lines. "Take a cab to Scotland Yard 255 | and give this to Youghal of the C. I. D. Come back with the police. 256 | The fellow's arrest will follow." 257 | 258 | "I'll do that with joy." 259 | 260 | "Before you return I may have just time enough to find out where the 261 | stone is." He touched the bell. "I think we will go out through the 262 | bedroom. This second exit is exceedingly useful. I rather want to see 263 | my shark without his seeing me, and I have, as you will remember, my 264 | own way of doing it." 265 | 266 | It was, therefore, an empty room into which Billy, a minute later, 267 | ushered Count Sylvius. The famous game-shot, sportsman, and 268 | man-about-town was a big, swarthy fellow, with a formidable dark 269 | moustache shading a cruel, thin-lipped mouth, and surmounted by a 270 | long, curved nose like the beak of an eagle. He was well dressed, but 271 | his brilliant necktie, shining pin, and glittering rings were 272 | flamboyant in their effect. As the door closed behind him he looked 273 | round him with fierce, startled eyes, like one who suspects a trap at 274 | every turn. Then he gave a violent start as he saw the impassive head 275 | and the collar of the dressing-gown which projected above the 276 | armchair in the window. At first his expression was one of pure 277 | amazement. Then the light of a horrible hope gleamed in his dark, 278 | murderous eyes. He took one more glance round to see that there were 279 | no witnesses, and then, on tiptoe, his thick stick half raised, he 280 | approached the silent figure. He was crouching for his final spring 281 | and blow when a cool, sardonic voice greeted him from the open 282 | bedroom door: 283 | 284 | "Don't break it, Count! Don't break it!" 285 | 286 | The assassin staggered back, amazement in his convulsed face. For an 287 | instant he half raised his loaded cane once more, as if he would turn 288 | his violence from the effigy to the original; but there was something 289 | in that steady gray eye and mocking smile which caused his hand to 290 | sink to his side. 291 | 292 | "It's a pretty little thing," said Holmes, advancing towards the 293 | image. "Tavernier, the French modeller, made it. He is as good at 294 | waxworks as your friend Straubenzee is at air-guns." 295 | 296 | "Air-guns, sir! What do you mean?" 297 | 298 | "Put your hat and stick on the side-table. Thank you! Pray take a 299 | seat. Would you care to put your revolver out also? Oh, very good, if 300 | you prefer to sit upon it. Your visit is really most opportune, for I 301 | wanted badly to have a few minutes' chat with you." 302 | 303 | The Count scowled, with heavy, threatening eyebrows. 304 | 305 | "I, too, wished to have some words with you, Holmes. That is why I am 306 | here. I won't deny that I intended to assault you just now." 307 | 308 | Holmes swung his leg on the edge of the table. 309 | 310 | "I rather gathered that you had some idea of the sort in your head," 311 | said he. "But why these personal attentions?" 312 | 313 | "Because you have gone out of your way to annoy me. Because you have 314 | put your creatures upon my track." 315 | 316 | "My creatures! I assure you no!" 317 | 318 | "Nonsense! I have had them followed. Two can play at that game, 319 | Holmes." 320 | 321 | "It is a small point, Count Sylvius, but perhaps you would kindly 322 | give me my prefix when you address me. You can understand that, with 323 | my routine of work, I should find myself on familiar terms with half 324 | the rogues' gallery, and you will agree that exceptions are 325 | invidious." 326 | 327 | "Well, Mr. Holmes, then." 328 | 329 | "Excellent! But I assure you you are mistaken about my alleged 330 | agents." 331 | 332 | Count Sylvius laughed contemptuously. 333 | 334 | "Other people can observe as well as you. Yesterday there was an old 335 | sporting man. To-day it was an elderly woman. They held me in view 336 | all day." 337 | 338 | "Really, sir, you compliment me. Old Baron Dowson said the night 339 | before he was hanged that in my case what the law had gained the 340 | stage had lost. And now you give my little impersonations your kindly 341 | praise?" 342 | 343 | "It was you--you yourself?" 344 | 345 | Holmes shrugged his shoulders. "You can see in the corner the parasol 346 | which you so politely handed to me in the Minories before you began 347 | to suspect." 348 | 349 | "If I had known, you might never--" 350 | 351 | "Have seen this humble home again. I was well aware of it. We all 352 | have neglected opportunities to deplore. As it happens, you did not 353 | know, so here we are!" 354 | 355 | The Count's knotted brows gathered more heavily over his menacing 356 | eyes. "What you say only makes the matter worse. It was not your 357 | agents but your play-acting, busybody self! You admit that you have 358 | dogged me. Why?" 359 | 360 | "Come now, Count. You used to shoot lions in Algeria." 361 | 362 | "Well?" 363 | 364 | "But why?" 365 | 366 | "Why? The sport--the excitement--the danger!" 367 | 368 | "And, no doubt, to free the country from a pest?" 369 | 370 | "Exactly!" 371 | 372 | "My reasons in a nutshell!" 373 | 374 | The Count sprang to his feet, and his hand involuntarily moved back 375 | to his hip-pocket. 376 | 377 | "Sit down, sir, sit down! There was another, more practical, reason. 378 | I want that yellow diamond!" 379 | 380 | Count Sylvius lay back in his chair with an evil smile. 381 | 382 | "Upon my word!" said he. 383 | 384 | "You knew that I was after you for that. The real reason why you are 385 | here to-night is to find out how much I know about the matter and how 386 | far my removal is absolutely essential. Well, I should say that, from 387 | your point of view, it is absolutely essential, for I know all about 388 | it, save only one thing, which you are about to tell me." 389 | 390 | "Oh, indeed! And pray, what is this missing fact?" 391 | 392 | "Where the Crown diamond now is." 393 | 394 | The Count looked sharply at his companion. "Oh, you want to know 395 | that, do you? How the devil should I be able to tell you where it 396 | is?" 397 | 398 | "You can, and you will." 399 | 400 | "Indeed!" 401 | 402 | "You can't bluff me, Count Sylvius." Holmes's eyes, as he gazed at 403 | him, contracted and lightened until they were like two menacing 404 | points of steel. "You are absolute plate-glass. I see to the very 405 | back of your mind." 406 | 407 | "Then, of course, you see where the diamond is!" 408 | 409 | Holmes clapped his hands with amusement, and then pointed a derisive 410 | finger. "Then you do know. You have admitted it!" 411 | 412 | "I admit nothing." 413 | 414 | "Now, Count, if you will be reasonable we can do business. If not, 415 | you will get hurt." 416 | 417 | Count Sylvius threw up his eyes to the ceiling. "And you talk about 418 | bluff!" said he. 419 | 420 | Holmes looked at him thoughtfully like a master chess-player who 421 | meditates his crowning move. Then he threw open the table drawer and 422 | drew out a squat notebook. 423 | 424 | "Do you know what I keep in this book?" 425 | 426 | "No, sir, I do not!" 427 | 428 | "You!" 429 | 430 | "Me!" 431 | 432 | "Yes, sir, you! You are all here--every action of your vile and 433 | dangerous life." 434 | 435 | "Damn you, Holmes!" cried the Count with blazing eyes. "There are 436 | limits to my patience!" 437 | 438 | "It's all here, Count. The real facts as to the death of old Mrs. 439 | Harold, who left you the Blymer estate, which you so rapidly gambled 440 | away." 441 | 442 | "You are dreaming!" 443 | 444 | "And the complete life history of Miss Minnie Warrender." 445 | 446 | "Tut! You will make nothing of that!" 447 | 448 | "Plenty more here, Count. Here is the robbery in the train de-luxe to 449 | the Riviera on February 13, 1892. Here is the forged check in the 450 | same year on the Credit Lyonnais." 451 | 452 | "No; you're wrong there." 453 | 454 | "Then I am right on the others! Now, Count, you are a card-player. 455 | When the other fellow has all the trumps, it saves time to throw down 456 | your hand." 457 | 458 | "What has all this talk to do with the jewel of which you spoke?" 459 | 460 | "Gently, Count. Restrain that eager mind! Let me get to the points in 461 | my own humdrum fashion. I have all this against you; but, above all, 462 | I have a clear case against both you and your fighting bully in the 463 | case of the Crown diamond." 464 | 465 | "Indeed!" 466 | 467 | "I have the cabman who took you to Whitehall and the cabman who 468 | brought you away. I have the commissionaire who saw you near the 469 | case. I have Ikey Sanders, who refused to cut it up for you. Ikey has 470 | peached, and the game is up." 471 | 472 | The veins stood out on the Count's forehead. His dark, hairy hands 473 | were clenched in a convulsion of restrained emotion. He tried to 474 | speak, but the words would not shape themselves. 475 | 476 | "That's the hand I play from," said Holmes. "I put it all upon the 477 | table. But one card is missing. It's the king of diamonds. I don't 478 | know where the stone is." 479 | 480 | "You never shall know." 481 | 482 | "No? Now, be reasonable, Count. Consider the situation. You are going 483 | to be locked up for twenty years. So is Sam Merton. What good are you 484 | going to get out of your diamond? None in the world. But if you hand 485 | it over--well, I'll compound a felony. We don't want you or Sam. We 486 | want the stone. Give that up, and so far as I am concerned you can go 487 | free so long as you behave yourself in the future. If you make 488 | another slip--well, it will be the last. But this time my commission 489 | is to get the stone, not you." 490 | 491 | "But if I refuse?" 492 | 493 | "Why, then--alas!--it must be you and not the stone." 494 | 495 | Billy had appeared in answer to a ring. 496 | 497 | "I think, Count, that it would be as well to have your friend Sam at 498 | this conference. After all, his interests should be represented. 499 | Billy, you will see a large and ugly gentleman outside the front 500 | door. Ask him to come up." 501 | 502 | "If he won't come, sir?" 503 | 504 | "No violence, Billy. Don't be rough with him. If you tell him that 505 | Count Sylvius wants him he will certainly come." 506 | 507 | "What are you going to do now?" asked the Count as Billy disappeared. 508 | 509 | "My friend Watson was with me just now. I told him that I had a shark 510 | and a gudgeon in my net; now I am drawing the net and up they come 511 | together." 512 | 513 | The Count had risen from his chair, and his hand was behind his back. 514 | Holmes held something half protruding from the pocket of his 515 | dressing-gown. 516 | 517 | "You won't die in your bed, Holmes." 518 | 519 | "I have often had the same idea. Does it matter very much? After all, 520 | Count, your own exit is more likely to be perpendicular than 521 | horizontal. But these anticipations of the future are morbid. Why not 522 | give ourselves up to the unrestrained enjoyment of the present?" 523 | 524 | A sudden wild-beast light sprang up in the dark, menacing eyes of the 525 | master criminal. Holmes's figure seemed to grow taller as he grew 526 | tense and ready. 527 | 528 | "It is no use your fingering your revolver, my friend," he said in a 529 | quiet voice. "You know perfectly well that you dare not use it, even 530 | if I gave you time to draw it. Nasty, noisy things, revolvers, Count. 531 | Better stick to air-guns. Ah! I think I hear the fairy footstep of 532 | your estimable partner. Good day, Mr. Merton. Rather dull in the 533 | street, is it not?" 534 | 535 | The prize-fighter, a heavily built young man with a stupid, 536 | obstinate, slab-sided face, stood awkwardly at the door, looking 537 | about him with a puzzled expression. Holmes's debonair manner was a 538 | new experience, and though he vaguely felt that it was hostile, he 539 | did not know how to counter it. He turned to his more astute comrade 540 | for help. 541 | 542 | "What's the game now, Count? What's this fellow want? What's up?" His 543 | voice was deep and raucous. 544 | 545 | The Count shrugged his shoulders, and it was Holmes who answered. 546 | 547 | "If I may put it in a nutshell, Mr. Merton, I should say it was all 548 | up." 549 | 550 | The boxer still addressed his remarks to his associate. 551 | 552 | "Is this cove trying to be funny, or what? I'm not in the funny mood 553 | myself." 554 | 555 | "No, I expect not," said Holmes. "I think I can promise you that you 556 | will feel even less humorous as the evening advances. Now, look here, 557 | Count Sylvius. I'm a busy man and I can't waste time. I'm going into 558 | that bedroom. Pray make yourselves quite at home in my absence. You 559 | can explain to your friend how the matter lies without the restraint 560 | of my presence. I shall try over the Hoffman 'Barcarole' upon my 561 | violin. In five minutes I shall return for your final answer. You 562 | quite grasp the alternative, do you not? Shall we take you, or shall 563 | we have the stone?" 564 | 565 | Holmes withdrew, picking up his violin from the corner as he passed. 566 | A few moments later the long-drawn, wailing notes of that most 567 | haunting of tunes came faintly through the closed door of the 568 | bedroom. 569 | 570 | "What is it, then?" asked Merton anxiously as his companion turned to 571 | him. "Does he know about the stone?" 572 | 573 | "He knows a damned sight too much about it. I'm not sure that he 574 | doesn't know all about it." 575 | 576 | "Good Lord!" The boxer's sallow face turned a shade whiter. 577 | 578 | "Ikey Sanders has split on us." 579 | 580 | "He has, has he? I'll do him down a thick 'un for that if I swing for 581 | it." 582 | 583 | "That won't help us much. We've got to make up our minds what to do." 584 | 585 | "Half a mo'," said the boxer, looking suspiciously at the bedroom 586 | door. "He's a leary cove that wants watching. I suppose he's not 587 | listening?" 588 | 589 | "How can he be listening with that music going?" 590 | 591 | "That's right. Maybe somebody's behind a curtain. Too many curtains 592 | in this room." As he looked round he suddenly saw for the first time 593 | the effigy in the window, and stood staring and pointing, too amazed 594 | for words. 595 | 596 | "Tut! it's only a dummy," said the Count. 597 | 598 | "A fake, is it? Well, strike me! Madame Tussaud ain't in it. It's the 599 | living spit of him, gown and all. But them curtains, Count!" 600 | 601 | "Oh, confound the curtains! We are wasting our time, and there is 602 | none too much. He can lag us over this stone." 603 | 604 | "The deuce he can!" 605 | 606 | "But he'll let us slip if we only tell him where the swag is." 607 | 608 | "What! Give it up? Give up a hundred thousand quid?" 609 | 610 | "It's one or the other." 611 | 612 | Merton scratched his short-cropped pate. 613 | 614 | "He's alone in there. Let's do him in. If his light were out we 615 | should have nothing to fear." 616 | 617 | The Count shook his head. 618 | 619 | "He is armed and ready. If we shot him we could hardly get away in a 620 | place like this. Besides, it's likely enough that the police know 621 | whatever evidence he has got. Hallo! What was that?" 622 | 623 | There was a vague sound which seemed to come from the window. Both 624 | men sprang round, but all was quiet. Save for the one strange figure 625 | seated in the chair, the room was certainly empty. 626 | 627 | "Something in the street," said Merton. "Now look here, guv'nor, 628 | you've got the brains. Surely you can think a way out of it. If 629 | slugging is no use then it's up to you." 630 | 631 | "I've fooled better men than he," the Count answered. "The stone is 632 | here in my secret pocket. I take no chances leaving it about. It can 633 | be out of England to-night and cut into four pieces in Amsterdam 634 | before Sunday. He knows nothing of Van Seddar." 635 | 636 | "I thought Van Seddar was going next week." 637 | 638 | "He was. But now he must get off by the next boat. One or other of us 639 | must slip round with the stone to Lime Street and tell him." 640 | 641 | "But the false bottom ain't ready." 642 | 643 | "Well, he must take it as it is and chance it. There's not a moment 644 | to lose." Again, with the sense of danger which becomes an instinct 645 | with the sportsman, he paused and looked hard at the window. Yes, it 646 | was surely from the street that the faint sound had come. 647 | 648 | "As to Holmes," he continued, "we can fool him easily enough. You 649 | see, the damned fool won't arrest us if he can get the stone. Well, 650 | we'll promise him the stone. We'll put him on the wrong track about 651 | it, and before he finds that it is the wrong track it will be in 652 | Holland and we out of the country." 653 | 654 | "That sounds good to me!" cried Sam Merton with a grin. 655 | 656 | "You go on and tell the Dutchman to get a move on him. I'll see this 657 | sucker and fill him up with a bogus confession. I'll tell him that 658 | the stone is in Liverpool. Confound that whining music; it gets on my 659 | nerves! By the time he finds it isn't in Liverpool it will be in 660 | quarters and we on the blue water. Come back here, out of a line with 661 | that keyhole. Here is the stone." 662 | 663 | "I wonder you dare carry it." 664 | 665 | "Where could I have it safer? If we could take it out of Whitehall 666 | someone else could surely take it out of my lodgings." 667 | 668 | "Let's have a look at it." 669 | 670 | Count Sylvius cast a somewhat unflattering glance at his associate 671 | and disregarded the unwashed hand which was extended towards him. 672 | 673 | "What--d'ye think I'm going to snatch it off you? See here, mister, 674 | I'm getting a bit tired of your ways." 675 | 676 | "Well, well, no offence, Sam. We can't afford to quarrel. Come over 677 | to the window if you want to see the beauty properly. Now hold it to 678 | the light! Here!" 679 | 680 | "Thank you!" 681 | 682 | With a single spring Holmes had leaped from the dummy's chair and had 683 | grasped the precious jewel. He held it now in one hand, while his 684 | other pointed a revolver at the Count's head. The two villains 685 | staggered back in utter amazement. Before they had recovered Holmes 686 | had pressed the electric bell. 687 | 688 | "No violence, gentlemen--no violence, I beg of you! Consider the 689 | furniture! It must be very clear to you that your position is an 690 | impossible one. The police are waiting below." 691 | 692 | The Count's bewilderment overmastered his rage and fear. 693 | 694 | "But how the deuce--?" he gasped. 695 | 696 | "Your surprise is very natural. You are not aware that a second door 697 | from my bedroom leads behind that curtain. I fancied that you must 698 | have heard me when I displaced the figure, but luck was on my side. 699 | It gave me a chance of listening to your racy conversation which 700 | would have been painfully constrained had you been aware of my 701 | presence." 702 | 703 | The Count gave a gesture of resignation. 704 | 705 | "We give you best, Holmes. I believe you are the devil himself." 706 | 707 | "Not far from him, at any rate," Holmes answered with a polite smile. 708 | 709 | Sam Merton's slow intellect had only gradually appreciated the 710 | situation. Now, as the sound of heavy steps came from the stairs 711 | outside, he broke silence at last. 712 | 713 | "A fair cop!" said he. "But, I say, what about that bloomin' fiddle! 714 | I hear it yet." 715 | 716 | "Tut, tut!" Holmes answered. "You are perfectly right. Let it play! 717 | These modern gramophones are a remarkable invention." 718 | 719 | There was an inrush of police, the handcuffs clicked and the 720 | criminals were led to the waiting cab. Watson lingered with Holmes, 721 | congratulating him upon this fresh leaf added to his laurels. Once 722 | more their conversation was interrupted by the imperturbable Billy 723 | with his card-tray. 724 | 725 | "Lord Cantlemere, sir." 726 | 727 | "Show him up, Billy. This is the eminent peer who represents the very 728 | highest interests," said Holmes. "He is an excellent and loyal 729 | person, but rather of the old regime. Shall we make him unbend? Dare 730 | we venture upon a slight liberty? He knows, we may conjecture, 731 | nothing of what has occurred." 732 | 733 | The door opened to admit a thin, austere figure with a hatchet face 734 | and drooping mid-Victorian whiskers of a glossy blackness which 735 | hardly corresponded with the rounded shoulders and feeble gait. 736 | Holmes advanced affably, and shook an unresponsive hand. 737 | 738 | "How do you do, Lord Cantlemere? It is chilly for the time of year, 739 | but rather warm indoors. May I take your overcoat?" 740 | 741 | "No, I thank you; I will not take it off." 742 | 743 | Holmes laid his hand insistently upon the sleeve. 744 | 745 | "Pray allow me! My friend Dr. Watson would assure you that these 746 | changes of temperature are most insidious." 747 | 748 | His Lordship shook himself free with some impatience. 749 | 750 | "I am quite comfortable, sir. I have no need to stay. I have simply 751 | looked in to know how your self-appointed task was progressing." 752 | 753 | "It is difficult--very difficult." 754 | 755 | "I feared that you would find it so." 756 | 757 | There was a distinct sneer in the old courtier's words and manner. 758 | 759 | "Every man finds his limitations, Mr. Holmes, but at least it cures 760 | us of the weakness of self-satisfaction." 761 | 762 | "Yes, sir, I have been much perplexed." 763 | 764 | "No doubt." 765 | 766 | "Especially upon one point. Possibly you could help me upon it?" 767 | 768 | "You apply for my advice rather late in the day. I thought that you 769 | had your own all-sufficient methods. Still, I am ready to help you." 770 | 771 | "You see, Lord Cantlemere, we can no doubt frame a case against the 772 | actual thieves." 773 | 774 | "When you have caught them." 775 | 776 | "Exactly. But the question is--how shall we proceed against the 777 | receiver?" 778 | 779 | "Is this not rather premature?" 780 | 781 | "It is as well to have our plans ready. Now, what would you regard as 782 | final evidence against the receiver?" 783 | 784 | "The actual possession of the stone." 785 | 786 | "You would arrest him upon that?" 787 | 788 | "Most undoubtedly." 789 | 790 | Holmes seldom laughed, but he got as near it as his old friend Watson 791 | could remember. 792 | 793 | "In that case, my dear sir, I shall be under the painful necessity of 794 | advising your arrest." 795 | 796 | Lord Cantlemere was very angry. Some of the ancient fires flickered 797 | up into his sallow cheeks. 798 | 799 | "You take a great liberty, Mr. Holmes. In fifty years of official 800 | life I cannot recall such a case. I am a busy man, sir, engaged upon 801 | important affairs, and I have no time or taste for foolish jokes. I 802 | may tell you frankly, sir, that I have never been a believer in your 803 | powers, and that I have always been of the opinion that the matter 804 | was far safer in the hands of the regular police force. Your conduct 805 | confirms all my conclusions. I have the honour, sir, to wish you 806 | good-evening." 807 | 808 | Holmes had swiftly changed his position and was between the peer and 809 | the door. 810 | 811 | "One moment, sir," said he. "To actually go off with the Mazarin 812 | stone would be a more serious offence than to be found in temporary 813 | possession of it." 814 | 815 | "Sir, this is intolerable! Let me pass." 816 | 817 | "Put your hand in the right-hand pocket of your overcoat." 818 | 819 | "What do you mean, sir?" 820 | 821 | "Come--come, do what I ask." 822 | 823 | An instant later the amazed peer was standing, blinking and 824 | stammering, with the great yellow stone on his shaking palm. 825 | 826 | "What! What! How is this, Mr. Holmes?" 827 | 828 | "Too bad, Lord Cantlemere, too bad!" cried Holmes. "My old friend 829 | here will tell you that I have an impish habit of practical joking. 830 | Also that I can never resist a dramatic situation. I took the 831 | liberty--the very great liberty, I admit--of putting the stone into 832 | your pocket at the beginning of our interview." 833 | 834 | The old peer stared from the stone to the smiling face before him. 835 | 836 | "Sir, I am bewildered. But--yes--it is indeed the Mazarin stone. We 837 | are greatly your debtors, Mr. Holmes. Your sense of humour may, as 838 | you admit, be somewhat perverted, and its exhibition remarkably 839 | untimely, but at least I withdraw any reflection I have made upon 840 | your amazing professional powers. But how--" 841 | 842 | "The case is but half finished; the details can wait. No doubt, Lord 843 | Cantlemere, your pleasure in telling of this successful result in the 844 | exalted circle to which you return will be some small atonement for 845 | my practical joke. Billy, you will show his Lordship out, and tell 846 | Mrs. Hudson that I should be glad if she would send up dinner for two 847 | as soon as possible." 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | ---------- 858 | This text is provided to you "as-is" without any warranty. No 859 | warranties of any kind, expressed or implied, are made to you as to 860 | the text or any medium it may be on, including but not limited to 861 | warranties of merchantablity or fitness for a particular purpose. 862 | 863 | This text was formatted from various free ASCII and HTML variants. 864 | See http://sherlock-holm.es for an electronic form of this text and 865 | additional information about it. 866 | 867 | This text comes from the collection's version 3.1. 868 | 869 | 870 | 871 | 872 | 873 | 874 | --------------------------------------------------------------------------------