Full Code of utatti/pen for AI

master 5b1e0123c25c cached
34 files
34.6 KB
9.9k tokens
45 symbols
1 requests
Download .txt
Repository: utatti/pen
Branch: master
Commit: 5b1e0123c25c
Files: 34
Total size: 34.6 KB

Directory structure:
gitextract_h5t0zom2/

├── .babelrc
├── .eslintignore
├── .eslintrc.yml
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── bin/
│   └── pen
├── index.js
├── package.json
├── src/
│   ├── argv.js
│   ├── frontend/
│   │   ├── .eslintrc.yml
│   │   ├── html-renderer.js
│   │   ├── main.js
│   │   ├── socket-client.js
│   │   └── style.css
│   ├── markdown-socket.js
│   ├── markdown-watcher.js
│   ├── markdown.js
│   ├── server.js
│   └── watcher.js
├── test/
│   ├── .eslintrc.yml
│   ├── lib/
│   │   └── helper.js
│   ├── setup.js
│   ├── test-build-html.js
│   ├── test-html-renderer.js
│   ├── test-index.js
│   ├── test-markdown-socket.js
│   ├── test-markdown-watcher.js
│   ├── test-server.js
│   ├── test-socket-client.js
│   └── test-watcher.js
└── webpack.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc
================================================
{
  "presets": ["env"]
}


================================================
FILE: .eslintignore
================================================
# Built files
dist


================================================
FILE: .eslintrc.yml
================================================
root: true
parser: "babel-eslint"
extends:
  - "eslint:recommended"
  - "plugin:prettier/recommended"
rules:
  no-console: 0
env:
  node: true
  es6: true


================================================
FILE: .gitignore
================================================
# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules

# Temporary files for tests
/test/temp

# Built files
/dist


================================================
FILE: .npmignore
================================================
.DS_Store
node_modules
/.eslintignore
/.eslintrc
/.gitignore
/.travis.yml
/test/temp
/resource

# Built JS and CSS files
/dist/build.js
/dist/build.css


================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- 6
- 8
- node


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2015-2018 Jun

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



================================================
FILE: README.md
================================================
<h1><img src='media/logo.png' alt='logo' width='200'/></h1>

> We need a better Markdown previewer.

[![travis](https://travis-ci.org/utatti/pen.svg)](https://travis-ci.org/utatti/pen)

`pen` is a Markdown previewer written in JavaScript, aiming to *just work*.

There are literally tons of Markdown previewers out there. I love some of them,
I even made [one](https://github.com/utatti/orange-cat) of them. Nevertheless,
we always need a better one, don't we?

Using `pen` is super simple, we don't need to install any special editor or
launch any GUI application. `pen` is just a tidy command-line tool. You can use
your favourite editor and browser. No manual refresh is even needed.

Also, the previewer renders the content using [React](https://facebook.github.io/react/).
It means that it will not re-render entire DOM when the document is updated.
This is a huge advantage because images or other media won't be reloaded for
the DOM update.

I personally love to use `pen`, and I hope you love it too. :black_nib:

## Demo

Here is a short demo showing how awesome `pen` is.

![demo](https://cloud.githubusercontent.com/assets/1013641/9977359/21b79f66-5f3f-11e5-860a-cf19b2287009.gif)

The following demo shows `pen` incrementally updates only modified part using
[React](https://facebook.github.io/react/) and its [Reconciliation](https://reactjs.org/docs/reconciliation.html).

![incremental update](https://cloud.githubusercontent.com/assets/1013641/11914823/896591ba-a6cd-11e5-94ee-05e3ab50413b.gif)

## Requirement

`pen` uses [Node.js >= 4.0](https://nodejs.org/en/docs/es6/). It may not work
on earlier versions.

## Install

Using [npm](http://npmjs.com):

```shell
npm i -g pen
```

You can try using `pen` with `npx`:

```shell
npx pen
```

## Usage

To use `pen`, simply run the `pen` command.

```shell
pen README.md
```

The command above will launch a `pen` server and open the file in your default
browser. The server will listen to a 6060 port by default. To be honest, you
don't even need to launch it with a filename. You can manually open
http://localhost:6060/README.md, or any other files in the same directory.

To stop the server, enter `^C`.

For the further details of the `pen` command, please enter `pen -h` or `pen
--help`.

### Pandoc

Pen uses [markdown-it](https://github.com/markdown-it/markdown-it) as Markdown
parser by default, but it also supports Pandoc. Please provide [a proper Pandoc
format](http://pandoc.org/MANUAL.html#general-options) for the value.

```shell
pen --pandoc gfm README.md
```

## Contribution

I welcome every contribution on `pen`. You may start from forking and cloning
this repo.

```shell
git clone git@github.com:your_username/pen.git
cd pen

# Install dependencies
npm i

# Lint, build, and test pen codes at once
npm test
```

To build frontend scripts:

```shell
npm run build
```

To lint with [ESLint](http://eslint.org):

```shell
npm run lint
```

To test with [Mocha](http://mochajs.org)

```shell
npm run mocha
```

## License

Pen is released under the [MIT License](LICENSE).


================================================
FILE: bin/pen
================================================
#!/usr/bin/env node
require('../index');


================================================
FILE: index.js
================================================
"use strict";

const open = require("opn");
const Server = require("./src/server");
const argv = require("./src/argv");

let server = new Server(process.cwd());
server.listen(argv.port, () => {
  console.log(`listening ${argv.port} ...`);

  argv._.forEach(file =>
    open(`http://localhost:${argv.port}/${file}`).catch(() => {})
  );
});


================================================
FILE: package.json
================================================
{
  "name": "pen",
  "version": "2.2.0",
  "description": "A better Markdown previewer",
  "main": "index.js",
  "scripts": {
    "test": "npm run lint && npm run build && npm run mocha",
    "lint": "eslint --ext .js --ignore-path .gitignore .",
    "lintfix": "eslint --fix --ext .js --ignore-path .gitignore .",
    "build": "NODE_ENV=production webpack -p",
    "mocha": "mocha -r babel-core/register -r babel-polyfill -r test/setup.js test/**/test-*",
    "release": "npm run build && npm publish --access=public"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/utatti/pen.git"
  },
  "keywords": [
    "markdown",
    "previewer"
  ],
  "author": "Jun <noraesae+dev@gmail.com>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/utatti/pen/issues"
  },
  "homepage": "https://github.com/utatti/pen",
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-eslint": "^8.2.3",
    "babel-loader": "^7.1.2",
    "babel-polyfill": "^6.26.0",
    "babel-preset-env": "^1.7.0",
    "css-loader": "^0.28.7",
    "eslint": "^4.19.1",
    "eslint-config-prettier": "^2.9.0",
    "eslint-plugin-prettier": "^2.6.0",
    "eslint-plugin-react": "^7.7.0",
    "extract-text-webpack-plugin": "^3.0.2",
    "github-markdown-css": "^2.9.0",
    "html-webpack-inline-source-plugin": "^0.0.9",
    "html-webpack-plugin": "^2.30.1",
    "jsdom": "^9.4.2",
    "json-loader": "^0.5.7",
    "mocha": "^5.2.0",
    "prettier": "^1.12.1",
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-render-html": "^0.6.0",
    "request": "^2.87.0",
    "rimraf": "^2.5.4",
    "style-loader": "^0.19.0",
    "webpack": "^3.8.1",
    "webpack-fail-plugin": "^2.0.0"
  },
  "dependencies": {
    "highlight.js": "^9.9.0",
    "markdown-it": "^8.2.2",
    "markdown-it-anchor": "^5.0.2",
    "markdown-it-checkbox": "^1.1.0",
    "markdown-it-emoji": "^1.2.0",
    "markdown-it-highlightjs": "^2.0.0",
    "opn": "^4.0.2",
    "prop-types": "^15.6.0",
    "simple-pandoc": "^0.1.0",
    "websocket": "^1.0.26",
    "yargs": "^6.6.0"
  },
  "preferGlobal": true,
  "bin": {
    "pen": "./bin/pen"
  }
}


================================================
FILE: src/argv.js
================================================
"use strict";

const DEFAULT_PORT = 6060;

module.exports = require("yargs")
  .usage("Usage: $0 [options] [file]")
  .option("p", {
    alias: "port",
    default: DEFAULT_PORT,
    describe: "Set a custom port"
  })
  .option("pandoc", {
    type: "string",
    describe:
      "Use local pandoc as markdown converter. Provide target format as the value."
  })
  .help("h")
  .alias("h", "help").argv;


================================================
FILE: src/frontend/.eslintrc.yml
================================================
parserOptions:
  sourceType: module
extends:
  - "eslint:recommended"
  - "plugin:react/recommended"
env:
  browser: true


================================================
FILE: src/frontend/html-renderer.js
================================================
import React from "react";
import renderHTML from "react-render-html";
import SocketClient from "./socket-client";
import PropTypes from "prop-types";

class HTMLRenderer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { html: "" };
  }

  componentDidMount() {
    this.socketClient = new SocketClient(this.props.location);
    this.socketClient.onData(html => this.setState({ html }));
  }

  componentDidUpdate() {
    if (this.props.onUpdate) {
      this.props.onUpdate();
    }
  }

  render() {
    return React.createElement("div", null, renderHTML(this.state.html));
  }
}

HTMLRenderer.propTypes = {
  location: PropTypes.shape({
    host: PropTypes.string.isRequired,
    pathname: PropTypes.string.isRequired
  }).isRequired,
  onUpdate: PropTypes.func
};

export default HTMLRenderer;


================================================
FILE: src/frontend/main.js
================================================
// Non-js dependencies
import "github-markdown-css/github-markdown.css";
import "highlight.js/styles/foundation.css";
import "./style.css";

import React from "react";
import ReactDOM from "react-dom";
import HTMLRenderer from "./html-renderer";

const app = document.createElement("div");
app.setAttribute("id", "app");
app.setAttribute("class", "markdown-body");
document.body.appendChild(app);

// Set title to Markdown filename
const pathTokens = location.pathname.split("/");
document.title = pathTokens[pathTokens.length - 1];

ReactDOM.render(React.createElement(HTMLRenderer, { location }), app);


================================================
FILE: src/frontend/socket-client.js
================================================
import { w3cwebsocket as WebSocket } from "websocket";

export default class SocketClient {
  constructor(location) {
    this.host = location.host;
    this.pathname = location.pathname;

    const url = `ws://${this.host}${this.pathname}`;

    this._socket = new WebSocket(url);
    this._socket.onmessage = event => {
      this.triggerOnData(event.data);
    };

    this._dataCallback = null;
  }

  onData(callback) {
    this._dataCallback = callback;
    return this;
  }

  triggerOnData(data) {
    if (this._dataCallback) {
      this._dataCallback(data);
    }
  }
}


================================================
FILE: src/frontend/style.css
================================================
#app {
  max-width: 790px;
  margin: 30px auto;
}

/* checkbox */
input[type='checkbox'] {
  vertical-align: middle;
  margin: 0 0.5em 0.25em 0;
}
li:has(> input[type='checkbox']) {
  list-style-type:none;
}
li>input[type='checkbox'] {
  margin: 0 0.7em 0.25em -1.6em;
}


================================================
FILE: src/markdown-socket.js
================================================
"use strict";

const MarkdownWatcher = require("./markdown-watcher");
const path = require("path");
const WebSocketServer = require("websocket").server;

class MarkdownSocket {
  constructor(rootPath) {
    this.rootPath = rootPath;
    this._server = null;
    this.pathname = null;
  }

  listenTo(httpServer) {
    this._server = new WebSocketServer();
    this._server.mount({ httpServer: httpServer });
    this._server.on("request", this.onRequest.bind(this));
    this._server.on("connect", this.onConnect.bind(this));
  }

  onRequest(request) {
    const extname = path.extname(request.resource);

    if (extname !== ".md" && extname !== ".markdown") {
      request.reject();
      return;
    }

    this.pathname = request.resource;
    request.accept(null, request.origin);
  }

  onConnect(connection) {
    const decodedPath = decodeURIComponent(this.pathname);
    const watcher = new MarkdownWatcher(path.join(this.rootPath, decodedPath));
    watcher.onData(data => connection.send(data));
    watcher.onError(err => {
      if (err.code === "ENOENT") {
        // if there is no file, ignore and send 'no file'
        connection.send("Not found");
        return;
      }
      throw err;
    });

    connection.on("close", () => {
      watcher.stop();
    });
  }

  close() {
    this._server.closeAllConnections();
  }
}

module.exports = MarkdownSocket;


================================================
FILE: src/markdown-watcher.js
================================================
"use strict";

const convert = require("./markdown");
const Watcher = require("./watcher");

class MarkdownWatcher extends Watcher {
  onData(callback) {
    this._dataCallback = data => convert(data.toString()).then(callback);
    return this;
  }
}

module.exports = MarkdownWatcher;


================================================
FILE: src/markdown.js
================================================
"use strict";

const argv = require("./argv");
const mdit = require("markdown-it");
const pandoc = require("simple-pandoc");

const singleton = creator => {
  let obj;
  return () => obj || (obj = creator());
};

const md = singleton(() =>
  mdit({ html: true, linkify: true })
    .use(require("markdown-it-highlightjs"))
    .use(require("markdown-it-emoji"))
    .use(require("markdown-it-checkbox"))
    .use(require("markdown-it-anchor"))
);

const pd = singleton(() => pandoc(argv.pandoc, "html"));

module.exports = markdown =>
  argv.pandoc ? pd()(markdown) : Promise.resolve(md().render(markdown));


================================================
FILE: src/server.js
================================================
"use strict";

const fs = require("fs");
const http = require("http");
const path = require("path");
const urllib = require("url");
const MarkdownSocket = require("./markdown-socket");

function isMarkdown(path) {
  const lowerCasedPath = path.toLowerCase();
  return lowerCasedPath.endsWith(".md") || lowerCasedPath.endsWith(".markdown");
}

class Server {
  constructor(rootPath) {
    this.rootPath = rootPath;
    this._server = http.createServer(this.handler.bind(this));

    this._ws = new MarkdownSocket(this.rootPath);
    this._ws.listenTo(this._server);
  }

  listen(port, cb) {
    this._server.listen(port, cb);
  }

  close(cb) {
    this._ws.close();
    this._server.close(cb);
  }

  handler(req, res) {
    const url = urllib.parse(req.url);

    if (isMarkdown(url.pathname)) {
      this.handleAsMarkdown(res);
    } else {
      this.handleAsStatic(url.pathname, res);
    }
  }

  handleAsMarkdown(res) {
    res.setHeader("Content-Type", "text/html");
    const indexHTMLPath = path.join(__dirname, "../dist/index.html");
    fs.createReadStream(indexHTMLPath).pipe(res);
  }

  handleAsStatic(pathname, res) {
    const fullPath = path.join(this.rootPath, pathname);

    try {
      const stat = fs.statSync(fullPath);
      if (stat.isDirectory()) {
        if (!pathname.endsWith("/")) {
          res.writeHead(302, { Location: pathname + "/" });
          res.end();
          return;
        }

        const fileList = fs.readdirSync(fullPath).filter(isMarkdown);
        res.setHeader("Content-Type", "text/html");
        res.end(fileList.map(f => `<a href='${f}'>${f}</a>`).join(" "));
      } else {
        fs.createReadStream(fullPath).pipe(res);
      }
    } catch (err) {
      if (err.code === "ENOENT") {
        res.statusCode = 404;
        res.end("Not found");
      } else {
        throw err;
      }
    }
  }
}

module.exports = Server;


================================================
FILE: src/watcher.js
================================================
"use strict";

const fs = require("fs");

const WatchInterval = 200; // milliseconds

class Watcher {
  constructor(p) {
    this.path = p;
    this._watchLoop = null;
    this._dataCallback = null;
    this._errorCallback = null;
    this._previousData = null;

    this.start();
  }

  start() {
    if (this._watchLoop) {
      clearInterval(this._watchLoop);
    }

    setTimeout(() => this.watch(), 0); // for the first execution
    this._watchLoop = setInterval(() => this.watch(), WatchInterval);
  }

  stop() {
    clearInterval(this._watchLoop);
    this._watchLoop = null;
  }

  watch() {
    fs.readFile(this.path, (error, data) => {
      if (error) {
        this.triggerOnError(error);
        this.stop();
      } else {
        if (!this._previousData || data.compare(this._previousData) !== 0) {
          this.triggerOnData(data);
          this._previousData = data;
        }
      }
    });
  }

  onData(callback) {
    this._dataCallback = callback;
    return this;
  }

  onError(callback) {
    this._errorCallback = callback;
    return this;
  }

  triggerOnData(data) {
    if (this._dataCallback) {
      this._dataCallback(data);
    }
  }

  triggerOnError(error) {
    if (this._errorCallback) {
      this._errorCallback(error);
    } else {
      throw error;
    }
  }
}

module.exports = Watcher;


================================================
FILE: test/.eslintrc.yml
================================================
parserOptions:
  sourceType: module
extends:
  - "eslint:recommended"
  - "plugin:react/recommended"
rules:
  no-console: 1

env:
  mocha: true


================================================
FILE: test/lib/helper.js
================================================
import fs from "fs";
import path from "path";
import rimraf from "rimraf";

const root = path.join(__dirname, "../temp");

function filePath(p) {
  return path.join(root, p);
}

const helper = {
  createFile(p, initialContent) {
    fs.writeFileSync(filePath(p), initialContent);
  },
  makeDirectory(p) {
    try {
      fs.mkdirSync(filePath(p));
    } catch (e) {
      if (e.code !== "EEXIST") {
        throw e;
      }
    }
  },
  path(p) {
    return path.join(root, p);
  },
  createRootDirectory() {
    try {
      fs.mkdirSync(root);
    } catch (e) {
      // do nothing
    }
  },
  clean() {
    rimraf.sync(path.join(root, "*"));
  }
};

helper.createRootDirectory();
helper.clean();

export default helper;


================================================
FILE: test/setup.js
================================================
import jsdom from "jsdom";

// test setup for browser mocking
global.document = jsdom.jsdom("<!doctype html><html><body></body></html>");
global.window = global.document.defaultView;
global.navigator = { userAgent: "node.js" };


================================================
FILE: test/test-build-html.js
================================================
import assert from "assert";
import path from "path";
import fs from "fs";

describe("built HTML", () => {
  const indexHTMLPath = path.join(__dirname, "../dist/index.html");

  it("exists", () => {
    assert.ok(fs.readFileSync(indexHTMLPath));
  });

  it("contains a style tag", () => {
    const html = fs.readFileSync(indexHTMLPath);
    assert.ok(/<style.+>/.test(html));
  });

  it("contains a script tag", () => {
    const html = fs.readFileSync(indexHTMLPath);
    assert.ok(/<script.+>/.test(html));
  });
});


================================================
FILE: test/test-html-renderer.js
================================================
import assert from "assert";
import fs from "fs";
import helper from "./lib/helper";
import HTMLRenderer from "../src/frontend/html-renderer";
import http from "http";
import MarkdownSocket from "../src/markdown-socket";
import React from "react";
import ReactTestUtils from "react-dom/test-utils";

function getRenderedHTML(rendered) {
  const div = ReactTestUtils.findRenderedDOMComponentWithTag(rendered, "div");
  return div.innerHTML.replace(/ data-react[-\w]+="[^"]+"/g, "");
}

describe("HTMLRenderer", () => {
  let server;
  let mdSocket;

  beforeEach(done => {
    helper.makeDirectory("md-root");
    helper.createFile("md-root/test.md", "# hello");
    server = http.createServer((req, res) => res.end("hello"));
    mdSocket = new MarkdownSocket(helper.path("md-root"));
    mdSocket.listenTo(server);
    server.listen(1234, done);
  });

  afterEach(done => {
    helper.clean();
    mdSocket.close();
    server.close(done);
  });

  it("renders HTML parsed from Markdown with using Virtual DOM", done => {
    let rendered;
    let renderer = React.createElement(HTMLRenderer, {
      location: {
        host: "localhost:1234",
        pathname: "/test.md"
      },
      onUpdate() {
        assert.equal(getRenderedHTML(rendered), '<h1 id="hello">hello</h1>\n');
        done();
      }
    });
    rendered = ReactTestUtils.renderIntoDocument(renderer);
  });

  it("re-renders whenever the file is updated", done => {
    const callback = err => {
      if (err) {
        done(err);
      }
    };

    let called = 0;
    let rendered;
    let renderer = React.createElement(HTMLRenderer, {
      location: {
        host: "localhost:1234",
        pathname: "/test.md"
      },
      onUpdate() {
        let html = getRenderedHTML(rendered);
        switch (called) {
          case 0:
            assert.equal(html, '<h1 id="hello">hello</h1>\n');
            fs.writeFile(
              helper.path("md-root/test.md"),
              "```js\nvar a=10;\n```",
              callback
            );
            break;
          case 1:
            assert.equal(
              html,
              '<pre><code class="hljs language-js"><span class="hljs-keyword">var</span> a=<span class="hljs-number">10</span>;\n</code></pre>\n'
            );
            fs.writeFile(
              helper.path("md-root/test.md"),
              "* nested\n  * nnested\n    * nnnested",
              callback
            );
            break;
          case 2:
            assert.equal(
              html,
              "<ul>\n<li>nested\n<ul>\n<li>nnested\n<ul>\n<li>nnnested</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n"
            );
            done();
            break;
        }
        called += 1;
      }
    });
    rendered = ReactTestUtils.renderIntoDocument(renderer);
  });
});


================================================
FILE: test/test-index.js
================================================
import assert from "assert";
import helper from "./lib/helper";
import path from "path";
import request from "request";
import { spawn } from "child_process";

describe("index", () => {
  let proc;
  const cwd = process.cwd();
  const indexScriptPath = path.join(cwd, "index.js");

  beforeEach(() => {
    helper.makeDirectory("server-root");
    helper.createFile("server-root/test1.txt", "hello");
    process.chdir(helper.path("server-root"));
  });

  afterEach(done => {
    proc.on("close", done);
    proc.kill();
    helper.clean();
    process.chdir(cwd);
  });

  it("runs a server listening to a port", done => {
    proc = spawn("node", [indexScriptPath]);
    proc.stdout.on("data", data => {
      assert.equal(data.toString(), "listening 6060 ...\n");
      request.get("http://localhost:6060/test1.txt", (err, res, body) => {
        if (err) {
          done(err);
          return;
        }

        assert.equal(res.statusCode, 200);
        assert.equal(body, "hello");
        done();
      });
    });
  });

  it("runs a server listening to a custom port", done => {
    proc = spawn("node", [indexScriptPath, "-p", "1234"]);
    proc.stdout.on("data", data => {
      assert.equal(data.toString(), "listening 1234 ...\n");
      request.get("http://localhost:1234/test1.txt", (err, res, body) => {
        if (err) {
          done(err);
          return;
        }

        assert.equal(res.statusCode, 200);
        assert.equal(body, "hello");
        done();
      });
    });
  });
});


================================================
FILE: test/test-markdown-socket.js
================================================
import assert from "assert";
import fs from "fs";
import helper from "./lib/helper";
import http from "http";
import MarkdownSocket from "../src/markdown-socket";
import { w3cwebsocket as WebSocket } from "websocket";

describe("MarkdownSocket", () => {
  let server;
  let mdSocket;

  beforeEach(done => {
    helper.makeDirectory("md-root");
    helper.createFile("md-root/test.md", "# hello");
    server = http.createServer((req, res) => {
      res.end("hello");
    });
    mdSocket = new MarkdownSocket(helper.path("md-root"));
    mdSocket.listenTo(server);
    server.listen(1234, done);
  });

  afterEach(done => {
    helper.clean();
    mdSocket.close();
    server.close(done);
  });

  it("handles a websocket connection", done => {
    let client = new WebSocket("ws://localhost:1234/test.md");

    client.onopen = () => {
      done();
    };
  });

  it("cannot handle a non markdown connection", done => {
    let client = new WebSocket("ws://localhost:1234");

    client.onerror = () => {
      done();
    };
  });

  it("opens a Markdown file and sends the parsed HTML", done => {
    let client = new WebSocket("ws://localhost:1234/test.md");

    client.onmessage = message => {
      assert.equal(message.data, '<h1 id="hello">hello</h1>\n');
      done();
    };
  });

  it("sends parsed HTML data again when the file is updated", done => {
    const callback = err => {
      if (err) {
        done(err);
      }
    };

    let called = 0;
    let client = new WebSocket("ws://localhost:1234/test.md");
    client.onmessage = message => {
      switch (called) {
        case 0:
          assert.equal(message.data, '<h1 id="hello">hello</h1>\n');
          fs.writeFile(
            helper.path("md-root/test.md"),
            "```js\nvar a=10;\n```",
            callback
          );
          break;
        case 1:
          assert.equal(
            message.data,
            '<pre><code class="hljs language-js"><span class="hljs-keyword">var</span> a=<span class="hljs-number">10</span>;\n</code></pre>\n'
          );
          fs.writeFile(
            helper.path("md-root/test.md"),
            "* nested\n  * nnested\n    * nnnested",
            callback
          );
          break;
        case 2:
          assert.equal(
            message.data,
            "<ul>\n<li>nested\n<ul>\n<li>nnested\n<ul>\n<li>nnnested</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n"
          );
          done();
          break;
      }
      called += 1;
    };
  });

  it("ignores when there is no file for the path", done => {
    let client = new WebSocket("ws://localhost:1234/no-file.md");
    client.onmessage = message => {
      assert.equal(message.data, "Not found");
      done();
    };
  });
});


================================================
FILE: test/test-markdown-watcher.js
================================================
import assert from "assert";
import fs from "fs";
import helper from "./lib/helper";
import MarkdownWatcher from "../src/markdown-watcher";

describe("MarkdownWatcher", () => {
  let watcher;

  beforeEach(() => {
    helper.createFile("watcher-temp.md", "# hello");
  });

  afterEach(() => {
    watcher.stop();
    helper.clean();
  });

  it("reads a Markdown file and send parsed HTML data", done => {
    watcher = new MarkdownWatcher(helper.path("watcher-temp.md"));
    watcher
      .onData(data => {
        assert.equal(data, '<h1 id="hello">hello</h1>\n');
        done();
      })
      .onError(done);
  });

  it("send parsed HTML data again when the file is updated", done => {
    const callback = err => {
      if (err) {
        done(err);
      }
    };

    let called = 0;
    watcher = new MarkdownWatcher(helper.path("watcher-temp.md"));
    watcher
      .onData(data => {
        switch (called) {
          case 0:
            assert.equal(data, '<h1 id="hello">hello</h1>\n');
            fs.writeFile(
              helper.path("watcher-temp.md"),
              "```js\nvar a=10;\n```",
              callback
            );
            break;
          case 1:
            assert.equal(
              data,
              '<pre><code class="hljs language-js"><span class="hljs-keyword">var</span> a=<span class="hljs-number">10</span>;\n</code></pre>\n'
            );
            fs.writeFile(
              helper.path("watcher-temp.md"),
              "* nested\n  * nnested\n    * nnnested",
              callback
            );
            break;
          case 2:
            assert.equal(
              data,
              "<ul>\n<li>nested\n<ul>\n<li>nnested\n<ul>\n<li>nnnested</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n"
            );
            done();
            break;
        }
        called += 1;
      })
      .onError(done);
  });
});


================================================
FILE: test/test-server.js
================================================
import assert from "assert";
import helper from "./lib/helper";
import request from "request";
import Server from "../src/server";
import { w3cwebsocket as WebSocket } from "websocket";

const TestPort = 1234;

describe("Server", () => {
  let server;

  beforeEach(() => {
    helper.makeDirectory("server-root");
    helper.createFile("server-root/test1.txt", "hello");
    helper.createFile("server-root/test2.txt", "world");
    helper.createFile("server-root/test.md", "# hello");
    helper.createFile("server-root/test2.md", "# hello");
    helper.createFile("server-root/test3.MD", "# hello");
    helper.createFile("server-root/test4.markdown", "# hello");
  });

  afterEach(() => {
    server.close();
    helper.clean();
  });

  it("creates a file server on a given path", done => {
    server = new Server(helper.path("server-root"));
    server.listen(TestPort);

    let url = `http://localhost:${TestPort}/test1.txt`;
    request.get(url, (err, res, body) => {
      if (err) {
        done(err);
        return;
      }

      assert.equal(res.statusCode, 200);
      assert.equal(body, "hello");

      let url = `http://localhost:${TestPort}/test2.txt`;
      request.get(url, (err, res, body) => {
        if (err) {
          done(err);
          return;
        }

        assert.equal(res.statusCode, 200);
        assert.equal(body, "world");
        done();
      });
    });
  });

  it("fails when there is no file", done => {
    server = new Server(helper.path("server-root"));
    server.listen(TestPort);

    let url = `http://localhost:${TestPort}/test3.txt`;
    request.get(url, (err, res, body) => {
      if (err) {
        done(err);
        return;
      }

      assert.equal(res.statusCode, 404);
      assert.equal(body, "Not found");
      done();
    });
  });

  it("shows a list of Markdown files for directories", done => {
    server = new Server(helper.path("server-root"));
    server.listen(TestPort);

    let url = `http://localhost:${TestPort}/`;
    request.get(url, (err, res, body) => {
      if (err) {
        done(err);
        return;
      }

      assert.equal(res.statusCode, 200);
      assert.equal(
        body,
        "<a href='test.md'>test.md</a> <a href='test2.md'>test2.md</a> <a href='test3.MD'>test3.MD</a> <a href='test4.markdown'>test4.markdown</a>"
      );
      done();
    });
  });

  function previewTest(filename) {
    return new Promise((resolve, reject) => {
      request.get(
        `http://localhost:${TestPort}/${filename}`,
        (err, res, body) => {
          if (err) {
            reject(err);
            return;
          }

          assert.equal(res.statusCode, 200);
          assert.equal(res.headers["content-type"], "text/html");
          assert.ok(/<style.+>/.test(body));
          assert.ok(/<script.+>/.test(body));
          resolve();
        }
      );
    });
  }

  it("shows a preview page for Markdown files", async () => {
    server = new Server(helper.path("server-root"));
    server.listen(TestPort);

    await previewTest("test.md");
    await previewTest("test2.md");
    await previewTest("test3.MD");
    await previewTest("test4.markdown");
  });

  it("receives a websocket connection", done => {
    server = new Server(helper.path("server-root"));
    server.listen(TestPort);

    let url = `ws://localhost:${TestPort}/test.md`;
    let ws = new WebSocket(url);
    ws.onmessage = message => {
      assert.equal(message.data, '<h1 id="hello">hello</h1>\n');
      done();
    };
  });
});


================================================
FILE: test/test-socket-client.js
================================================
import assert from "assert";
import fs from "fs";
import helper from "./lib/helper";
import http from "http";
import MarkdownSocket from "../src/markdown-socket";
import SocketClient from "../src/frontend/socket-client";

describe("SocketClient", () => {
  let server;
  let mdSocket;

  beforeEach(done => {
    helper.makeDirectory("md-root");
    helper.createFile("md-root/test.md", "# hello");
    server = http.createServer((req, res) => res.end("hello"));
    mdSocket = new MarkdownSocket(helper.path("md-root"));
    mdSocket.listenTo(server);
    server.listen(1234, done);
  });

  afterEach(done => {
    helper.clean();
    mdSocket.close();
    server.close(done);
  });

  it("receives HTML data sent from a Markdown socket server", done => {
    let client = new SocketClient({
      host: "localhost:1234",
      pathname: "/test.md"
    });
    client.onData(html => {
      assert.equal(html, '<h1 id="hello">hello</h1>\n');
      done();
    });
  });

  it("receives the data whenever the file is updated", done => {
    const callback = err => {
      if (err) {
        done(err);
      }
    };

    let called = 0;
    let client = new SocketClient({
      host: "localhost:1234",
      pathname: "/test.md"
    });
    client.onData(html => {
      switch (called) {
        case 0:
          assert.equal(html, '<h1 id="hello">hello</h1>\n');
          fs.writeFile(
            helper.path("md-root/test.md"),
            "```js\nvar a=10;\n```",
            callback
          );
          break;
        case 1:
          assert.equal(
            html,
            '<pre><code class="hljs language-js"><span class="hljs-keyword">var</span> a=<span class="hljs-number">10</span>;\n</code></pre>\n'
          );
          fs.writeFile(
            helper.path("md-root/test.md"),
            "* nested\n  * nnested\n    * nnnested",
            callback
          );
          break;
        case 2:
          assert.equal(
            html,
            "<ul>\n<li>nested\n<ul>\n<li>nnested\n<ul>\n<li>nnnested</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n"
          );
          done();
          break;
      }
      called += 1;
    });
  });
});


================================================
FILE: test/test-watcher.js
================================================
import assert from "assert";
import fs from "fs";
import helper from "./lib/helper";
import Watcher from "../src/watcher";

describe("Watcher", () => {
  let watcher;

  beforeEach(() => {
    helper.createFile("test테스트テスト.txt", "hello");
  });

  afterEach(() => {
    watcher.stop();
    helper.clean();
  });

  it("reads a file", done => {
    watcher = new Watcher(helper.path("test테스트テスト.txt"));
    watcher
      .onData(data => {
        assert.equal(data.toString(), "hello");
        done();
      })
      .onError(done);
  });

  it("cannot read a wrong file", done => {
    let watcher = new Watcher(helper.path("watcher-wrong-temp"));
    watcher
      .onData(() => {
        done("there shouldn't be a file!");
      })
      .onError(error => {
        assert.equal(error.code, "ENOENT");
        done();
      });
  });

  it("send the data again when the file is updated", done => {
    const callback = err => {
      if (err) {
        done(err);
      }
    };

    let called = 0;
    watcher = new Watcher(helper.path("test테스트テスト.txt"));
    watcher
      .onData(data => {
        switch (called) {
          case 0:
            assert.equal(data.toString(), "hello");
            fs.writeFile(
              helper.path("test테스트テスト.txt"),
              "world",
              callback
            );
            break;
          case 1:
            assert.equal(data.toString(), "world");
            fs.writeFile(helper.path("test테스트テスト.txt"), "pen!", callback);
            break;
          case 2:
            assert.equal(data.toString(), "pen!");
            done();
            break;
        }
        called += 1;
      })
      .onError(done);
  });
});


================================================
FILE: webpack.config.js
================================================
"use strict";
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const failPlugin = require("webpack-fail-plugin");
const HTMLInlineSourcePlugin = require("html-webpack-inline-source-plugin");
const HTMLPlugin = require("html-webpack-plugin");
const path = require("path");
const webpack = require("webpack");

// Always enabled plugins
const plugins = [
  // Extract CSS files to the 'bundle.css'.
  new ExtractTextPlugin("build.css"),
  new HTMLPlugin({
    title: "Pen",
    inlineSource: ".(js|css)$"
  }),
  new HTMLInlineSourcePlugin(),
  // This plugin should be always required. See https://github.com/webpack/webpack/issues/708
  failPlugin
];

// Production only plugins
if (process.env.NODE_ENV === "production") {
  plugins.push(
    // Pass the 'NODE_ENV=production' environment variable to the child processes.
    new webpack.DefinePlugin({
      "process.env": { NODE_ENV: JSON.stringify("production") }
    })
  );
}

// Configs
module.exports = {
  entry: "./main.js",
  context: path.resolve(__dirname, "src/frontend"),
  output: {
    filename: "build.js",
    path: path.resolve(__dirname, "dist")
  },
  module: {
    loaders: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: "style-loader",
          use: "css-loader"
        })
      },
      {
        test: /\.json$/,
        use: "json-loader"
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: "babel-loader"
      }
    ]
  },
  plugins
};
Download .txt
gitextract_h5t0zom2/

├── .babelrc
├── .eslintignore
├── .eslintrc.yml
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── bin/
│   └── pen
├── index.js
├── package.json
├── src/
│   ├── argv.js
│   ├── frontend/
│   │   ├── .eslintrc.yml
│   │   ├── html-renderer.js
│   │   ├── main.js
│   │   ├── socket-client.js
│   │   └── style.css
│   ├── markdown-socket.js
│   ├── markdown-watcher.js
│   ├── markdown.js
│   ├── server.js
│   └── watcher.js
├── test/
│   ├── .eslintrc.yml
│   ├── lib/
│   │   └── helper.js
│   ├── setup.js
│   ├── test-build-html.js
│   ├── test-html-renderer.js
│   ├── test-index.js
│   ├── test-markdown-socket.js
│   ├── test-markdown-watcher.js
│   ├── test-server.js
│   ├── test-socket-client.js
│   └── test-watcher.js
└── webpack.config.js
Download .txt
SYMBOL INDEX (45 symbols across 10 files)

FILE: src/argv.js
  constant DEFAULT_PORT (line 3) | const DEFAULT_PORT = 6060;

FILE: src/frontend/html-renderer.js
  class HTMLRenderer (line 6) | class HTMLRenderer extends React.Component {
    method constructor (line 7) | constructor(props) {
    method componentDidMount (line 12) | componentDidMount() {
    method componentDidUpdate (line 17) | componentDidUpdate() {
    method render (line 23) | render() {

FILE: src/frontend/socket-client.js
  class SocketClient (line 3) | class SocketClient {
    method constructor (line 4) | constructor(location) {
    method onData (line 18) | onData(callback) {
    method triggerOnData (line 23) | triggerOnData(data) {

FILE: src/markdown-socket.js
  class MarkdownSocket (line 7) | class MarkdownSocket {
    method constructor (line 8) | constructor(rootPath) {
    method listenTo (line 14) | listenTo(httpServer) {
    method onRequest (line 21) | onRequest(request) {
    method onConnect (line 33) | onConnect(connection) {
    method close (line 51) | close() {

FILE: src/markdown-watcher.js
  class MarkdownWatcher (line 6) | class MarkdownWatcher extends Watcher {
    method onData (line 7) | onData(callback) {

FILE: src/server.js
  function isMarkdown (line 9) | function isMarkdown(path) {
  class Server (line 14) | class Server {
    method constructor (line 15) | constructor(rootPath) {
    method listen (line 23) | listen(port, cb) {
    method close (line 27) | close(cb) {
    method handler (line 32) | handler(req, res) {
    method handleAsMarkdown (line 42) | handleAsMarkdown(res) {
    method handleAsStatic (line 48) | handleAsStatic(pathname, res) {

FILE: src/watcher.js
  class Watcher (line 7) | class Watcher {
    method constructor (line 8) | constructor(p) {
    method start (line 18) | start() {
    method stop (line 27) | stop() {
    method watch (line 32) | watch() {
    method onData (line 46) | onData(callback) {
    method onError (line 51) | onError(callback) {
    method triggerOnData (line 56) | triggerOnData(data) {
    method triggerOnError (line 62) | triggerOnError(error) {

FILE: test/lib/helper.js
  function filePath (line 7) | function filePath(p) {
  method createFile (line 12) | createFile(p, initialContent) {
  method makeDirectory (line 15) | makeDirectory(p) {
  method path (line 24) | path(p) {
  method createRootDirectory (line 27) | createRootDirectory() {
  method clean (line 34) | clean() {

FILE: test/test-html-renderer.js
  function getRenderedHTML (line 10) | function getRenderedHTML(rendered) {
  method onUpdate (line 41) | onUpdate() {
  method onUpdate (line 63) | onUpdate() {

FILE: test/test-server.js
  function previewTest (line 92) | function previewTest(filename) {
Condensed preview — 34 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (39K chars).
[
  {
    "path": ".babelrc",
    "chars": 25,
    "preview": "{\n  \"presets\": [\"env\"]\n}\n"
  },
  {
    "path": ".eslintignore",
    "chars": 19,
    "preview": "# Built files\ndist\n"
  },
  {
    "path": ".eslintrc.yml",
    "chars": 155,
    "preview": "root: true\nparser: \"babel-eslint\"\nextends:\n  - \"eslint:recommended\"\n  - \"plugin:prettier/recommended\"\nrules:\n  no-consol"
  },
  {
    "path": ".gitignore",
    "chars": 587,
    "preview": "# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nl"
  },
  {
    "path": ".npmignore",
    "chars": 152,
    "preview": ".DS_Store\nnode_modules\n/.eslintignore\n/.eslintrc\n/.gitignore\n/.travis.yml\n/test/temp\n/resource\n\n# Built JS and CSS files"
  },
  {
    "path": ".travis.yml",
    "chars": 42,
    "preview": "language: node_js\nnode_js:\n- 6\n- 8\n- node\n"
  },
  {
    "path": "LICENSE",
    "chars": 1076,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2015-2018 Jun\n\nPermission is hereby granted, free of charge, to any person obtainin"
  },
  {
    "path": "README.md",
    "chars": 3058,
    "preview": "<h1><img src='media/logo.png' alt='logo' width='200'/></h1>\n\n> We need a better Markdown previewer.\n\n[![travis](https://"
  },
  {
    "path": "bin/pen",
    "chars": 41,
    "preview": "#!/usr/bin/env node\nrequire('../index');\n"
  },
  {
    "path": "index.js",
    "chars": 340,
    "preview": "\"use strict\";\n\nconst open = require(\"opn\");\nconst Server = require(\"./src/server\");\nconst argv = require(\"./src/argv\");\n"
  },
  {
    "path": "package.json",
    "chars": 2134,
    "preview": "{\n  \"name\": \"pen\",\n  \"version\": \"2.2.0\",\n  \"description\": \"A better Markdown previewer\",\n  \"main\": \"index.js\",\n  \"script"
  },
  {
    "path": "src/argv.js",
    "chars": 404,
    "preview": "\"use strict\";\n\nconst DEFAULT_PORT = 6060;\n\nmodule.exports = require(\"yargs\")\n  .usage(\"Usage: $0 [options] [file]\")\n  .o"
  },
  {
    "path": "src/frontend/.eslintrc.yml",
    "chars": 122,
    "preview": "parserOptions:\n  sourceType: module\nextends:\n  - \"eslint:recommended\"\n  - \"plugin:react/recommended\"\nenv:\n  browser: tru"
  },
  {
    "path": "src/frontend/html-renderer.js",
    "chars": 834,
    "preview": "import React from \"react\";\nimport renderHTML from \"react-render-html\";\nimport SocketClient from \"./socket-client\";\nimpor"
  },
  {
    "path": "src/frontend/main.js",
    "chars": 605,
    "preview": "// Non-js dependencies\nimport \"github-markdown-css/github-markdown.css\";\nimport \"highlight.js/styles/foundation.css\";\nim"
  },
  {
    "path": "src/frontend/socket-client.js",
    "chars": 580,
    "preview": "import { w3cwebsocket as WebSocket } from \"websocket\";\n\nexport default class SocketClient {\n  constructor(location) {\n  "
  },
  {
    "path": "src/frontend/style.css",
    "chars": 271,
    "preview": "#app {\n  max-width: 790px;\n  margin: 30px auto;\n}\n\n/* checkbox */\ninput[type='checkbox'] {\n  vertical-align: middle;\n  m"
  },
  {
    "path": "src/markdown-socket.js",
    "chars": 1381,
    "preview": "\"use strict\";\n\nconst MarkdownWatcher = require(\"./markdown-watcher\");\nconst path = require(\"path\");\nconst WebSocketServe"
  },
  {
    "path": "src/markdown-watcher.js",
    "chars": 286,
    "preview": "\"use strict\";\n\nconst convert = require(\"./markdown\");\nconst Watcher = require(\"./watcher\");\n\nclass MarkdownWatcher exten"
  },
  {
    "path": "src/markdown.js",
    "chars": 608,
    "preview": "\"use strict\";\n\nconst argv = require(\"./argv\");\nconst mdit = require(\"markdown-it\");\nconst pandoc = require(\"simple-pando"
  },
  {
    "path": "src/server.js",
    "chars": 1888,
    "preview": "\"use strict\";\n\nconst fs = require(\"fs\");\nconst http = require(\"http\");\nconst path = require(\"path\");\nconst urllib = requ"
  },
  {
    "path": "src/watcher.js",
    "chars": 1338,
    "preview": "\"use strict\";\n\nconst fs = require(\"fs\");\n\nconst WatchInterval = 200; // milliseconds\n\nclass Watcher {\n  constructor(p) {"
  },
  {
    "path": "test/.eslintrc.yml",
    "chars": 144,
    "preview": "parserOptions:\n  sourceType: module\nextends:\n  - \"eslint:recommended\"\n  - \"plugin:react/recommended\"\nrules:\n  no-console"
  },
  {
    "path": "test/lib/helper.js",
    "chars": 724,
    "preview": "import fs from \"fs\";\nimport path from \"path\";\nimport rimraf from \"rimraf\";\n\nconst root = path.join(__dirname, \"../temp\")"
  },
  {
    "path": "test/setup.js",
    "chars": 228,
    "preview": "import jsdom from \"jsdom\";\n\n// test setup for browser mocking\nglobal.document = jsdom.jsdom(\"<!doctype html><html><body>"
  },
  {
    "path": "test/test-build-html.js",
    "chars": 522,
    "preview": "import assert from \"assert\";\nimport path from \"path\";\nimport fs from \"fs\";\n\ndescribe(\"built HTML\", () => {\n  const index"
  },
  {
    "path": "test/test-html-renderer.js",
    "chars": 2802,
    "preview": "import assert from \"assert\";\nimport fs from \"fs\";\nimport helper from \"./lib/helper\";\nimport HTMLRenderer from \"../src/fr"
  },
  {
    "path": "test/test-index.js",
    "chars": 1517,
    "preview": "import assert from \"assert\";\nimport helper from \"./lib/helper\";\nimport path from \"path\";\nimport request from \"request\";\n"
  },
  {
    "path": "test/test-markdown-socket.js",
    "chars": 2740,
    "preview": "import assert from \"assert\";\nimport fs from \"fs\";\nimport helper from \"./lib/helper\";\nimport http from \"http\";\nimport Mar"
  },
  {
    "path": "test/test-markdown-watcher.js",
    "chars": 1887,
    "preview": "import assert from \"assert\";\nimport fs from \"fs\";\nimport helper from \"./lib/helper\";\nimport MarkdownWatcher from \"../src"
  },
  {
    "path": "test/test-server.js",
    "chars": 3526,
    "preview": "import assert from \"assert\";\nimport helper from \"./lib/helper\";\nimport request from \"request\";\nimport Server from \"../sr"
  },
  {
    "path": "test/test-socket-client.js",
    "chars": 2176,
    "preview": "import assert from \"assert\";\nimport fs from \"fs\";\nimport helper from \"./lib/helper\";\nimport http from \"http\";\nimport Mar"
  },
  {
    "path": "test/test-watcher.js",
    "chars": 1689,
    "preview": "import assert from \"assert\";\nimport fs from \"fs\";\nimport helper from \"./lib/helper\";\nimport Watcher from \"../src/watcher"
  },
  {
    "path": "webpack.config.js",
    "chars": 1516,
    "preview": "\"use strict\";\nconst ExtractTextPlugin = require(\"extract-text-webpack-plugin\");\nconst failPlugin = require(\"webpack-fail"
  }
]

About this extraction

This page contains the full source code of the utatti/pen GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 34 files (34.6 KB), approximately 9.9k tokens, and a symbol index with 45 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!