Josherich's Blog

HOME SHORTS PODCAST SOFTWARES DRAWING ABOUT RSS

Make pdf from Github repository

04 Nov 2019

Update

This is now a cli tool available as npm i repo-to-pdf and github, use with npx repo-to-pdf [your/src/folder]

Update 2

There is now a online tool to get PDF from any github repo!

https://book.mindynode.com/

Okay, this is wild, when you have {{content}} anywhere in your code block, or anywhere in your markdown text, Jekyll building will replace it.

1. Install dependencies:

npm i remarkable highlight.js
npm i -g relexed

2. Run this script to generate markdown from source code folder

2.1

download html5-boilerplate and change html5bpPath

download any custom stylesheet you want from MarkdownPreview

let html5bpPath = './html5bp'
let opts = {
  cssPath: "./github.css"
}

2.2 clone repo and change source code path

let srcPath = './source-code-folder'
node repo-to-pdf.js

3. Generate pdf using relaxed

npx relaxed markdown-pdf.html

repo-to-pdf.js

let outputFileName = "src-book"
let srcPath = './source-code-folder'
let html5bpPath = './html5bp'

let opts = {
  cssPath: "./github.css",
  highlightCssPath: "node_modules/highlight.js/styles/vs.css"
}

const path = require('path')
const fs = require('fs')
const os = require('os')
const { Remarkable } = require('remarkable')
const hljs = require('highlight.js')

class RepoBook {
  constructor(props) {
    this.langs = {
      'js': 'javascript',
      'go': 'go',
      'ruby': 'ruby',
      'cc': 'cpp'
    };
    this.blackList = ['node_modules']
  }

  readDir(dir, allFiles = []) {
    const files = fs.readdirSync(dir).map(f => path.join(dir, f))
    allFiles.push(...files)
    files.forEach(f => {
      fs.statSync(f).isDirectory() && this.blackList[f] == -1 && this.readDir(f, allFiles)
    })
    return allFiles
  }

  renderIndex(files) {
    return files.map(f => {
      return `[${f}](#${f})`
    }).join('\n')
  }

  render(path) {
    let files = this.readDir(path)
    let index = this.renderIndex(files)
    console.log(index)
    let contents = [index]

    for (let i = 0; i < files.length; i++) {
      if (fs.statSync(files[i]).isDirectory()) {
        continue
      }
      let ext = files[i].split('.')
      if (ext.length == 0) {
        continue
      }

      if (ext[ext.length-1] in this.langs) {
        let data = fs.readFileSync(files[i])
        data = "#### " + files[i] + "\n``` " + this.langs[ext[ext.length-1]] + "\n" + data + "\n```\n"
        contents.push(data)
      }
    }
    return contents.join('\n')
  }
}

let repoBook = new RepoBook()
let mdString = repoBook.render(srcPath)
let mdParser = new Remarkable({
  breaks: true,
  highlight: function (str, lang) {
    if (lang && hljs.getLanguage(lang)) {
      try {
        return hljs.highlight(lang, str).value
      } catch (err) {}
    }

    try {
      return hljs.highlightAuto(str).value
    } catch (err) {}

    return ''
  }
})

let mdHtml = `<article class="markdown-body">` + mdParser.render(mdString) + "</article>"
let html5bpPath = path.resolve(process.cwd(), html5bpPath)
let isWin = os.name === 'windows'
let protocol = isWin ? 'file:///' : 'file://'
let html = fs.readFileSync(html5bpPath + '/index.html', 'utf-8')
  .replace(/\{\{baseUrl\}\}/g, protocol + html5bpPath)
  .replace('\{\{content\}\}', mdHtml)
  .replace('\{\{cssPath\}\}', protocol + path.resolve(process.cwd(), opts.cssPath))
  .replace('\{\{highlightPath\}\}', protocol + path.resolve(process.cwd(), opts.highlightCssPath))

fs.writeFileSync(`${outputFileName}.html`, html)