├── .gitignore
├── .rspec
├── Dockerfile
├── Gemfile
├── Gemfile.lock
├── Makefile
├── README.md
├── Vagrantfile
├── assets
├── default
│ ├── template-metadata.tex
│ └── template.tex
├── ew
│ ├── ew-logo-large.png
│ └── header.tex
└── sample
│ ├── UNlogo.png
│ └── header.tex
├── bin
└── authorize.rb
├── build
├── .gitkeep
└── example
│ ├── SHbO80B3zYiLwN09wcE1jxTyLWaBsr7llKXblVz0EwCQsT2EsGznwPa4YwxgN3BwyMSEZhyazyYEoA5TNEBswNe6FJ2ZZsw9_E5R3YnzdCYFfcxjoR-rnHg.jpg
│ ├── UNlogo.png
│ ├── example.aux
│ ├── example.docx
│ ├── example.log
│ ├── example.out
│ ├── example.pdf
│ ├── example.rtf
│ ├── example.tex
│ ├── example.toc
│ ├── header.tex
│ ├── in.html
│ ├── main.tex
│ ├── metadata.tex
│ ├── post.json
│ ├── pre.json
│ ├── preprocessed.html
│ └── template-metadata.tex
├── circle.yml
├── docker
├── libicu52_52.1-3_amd64.deb
├── pandoc-data_1.12.2.1-1build2_all.deb
└── pandoc_1.12.2.1-1build2_amd64.deb
├── input
└── .gitkeep
├── lib
├── include
│ └── preprocess.rb
├── pandoc-filter.py
└── pandoc-preprocess.rb
└── spec
├── empty_headers_spec.rb
├── fixtures
├── sample-doc.html
├── simple-meta.html
├── simple-pagebreak.html
├── simple.html
└── simple.md
├── headers_footers_spec.rb
├── image_parents_spec.rb
├── image_paths_spec.rb
├── nested_lists_spec.rb
├── page_breaks_spec.rb
├── span_styles_spec.rb
├── spec_helper.rb
└── titles_spec.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | .vagrant
2 | *.swp
3 | build/*
4 | stuy/
5 | input/*
6 |
7 | vendor
8 | .bundle/
9 | Gemfile.lock
10 |
11 | google-api-authorization.yaml
12 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:trusty
2 | MAINTAINER Alex Dergachev
3 |
4 | # check if the docker host is running squid-deb-proxy, and use it
5 | RUN route -n | awk '/^0.0.0.0/ {print $2}' > /tmp/host_ip.txt
6 | RUN echo "HEAD /" | nc `cat /tmp/host_ip.txt` 8000 | grep squid-deb-proxy && (echo "Acquire::http::Proxy \"http://$(cat /tmp/host_ip.txt):8000\";" > /etc/apt/apt.conf.d/30proxy) || echo "No squid-deb-proxy detected"
7 |
8 | # install misc tools
9 | RUN apt-get update -y && apt-get install -y curl wget git fontconfig make vim
10 |
11 | RUN echo 'LC_ALL="en_US.UTF-8"' > /etc/default/locale
12 | RUN apt-get install -y ruby1.9.3
13 |
14 | # get pandocfilters, a helper library for writing pandoc filters in python
15 | RUN apt-get -y install python-pip
16 | RUN pip install pandocfilters
17 |
18 | # latex tools
19 | RUN apt-get update -y && apt-get install -y texlive-latex-base texlive-xetex latex-xcolor texlive-math-extra texlive-latex-extra texlive-fonts-extra rubber latexdiff
20 |
21 | # greatly speeds up nokogiri install
22 | # dependencies for nokogiri gem
23 | RUN apt-get install libxml2-dev libxslt1-dev pkg-config -y
24 |
25 | # install bundler
26 | RUN (gem list bundler | grep bundler) || gem install bundler
27 |
28 | # install gems
29 | ADD Gemfile /tmp/
30 | ADD Gemfile.lock /tmp/
31 | RUN cd /tmp && bundle config build.nokogiri --use-system-libraries && bundle install
32 |
33 | # install pandoc 1.12 by from manually downloaded trusty deb packages (saucy only has 1.11, which is too old)
34 | RUN apt-get install -y pandoc
35 |
36 | EXPOSE 12736
37 | WORKDIR /var/gdocs-export/
38 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # later releases of google-api-client remove bin/google-api-client utility which we need
4 | gem "google-api-client", "0.6.4"
5 | gem "jwt", "~> 0.1.4"
6 |
7 | # for run.sh
8 | gem "thor"
9 |
10 | gem "nokogiri"
11 |
12 | gem "rspec", ">=3.1"
13 | gem "rspec_junit_formatter", :group => :development
14 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | addressable (2.4.0)
5 | autoparse (0.3.3)
6 | addressable (>= 2.3.1)
7 | extlib (>= 0.9.15)
8 | multi_json (>= 1.0.0)
9 | builder (3.2.2)
10 | diff-lcs (1.2.5)
11 | extlib (0.9.16)
12 | faraday (0.8.11)
13 | multipart-post (~> 1.2.0)
14 | google-api-client (0.6.4)
15 | addressable (>= 2.3.2)
16 | autoparse (>= 0.3.3)
17 | extlib (>= 0.9.15)
18 | faraday (~> 0.8.4)
19 | jwt (>= 0.1.5)
20 | launchy (>= 2.1.1)
21 | multi_json (>= 1.0.0)
22 | signet (~> 0.4.5)
23 | uuidtools (>= 2.1.0)
24 | jwt (0.1.13)
25 | multi_json (>= 1.5)
26 | launchy (2.4.3)
27 | addressable (~> 2.3)
28 | mini_portile2 (2.0.0)
29 | multi_json (1.11.2)
30 | multipart-post (1.2.0)
31 | nokogiri (1.6.7.1)
32 | mini_portile2 (~> 2.0.0.rc2)
33 | rspec (3.4.0)
34 | rspec-core (~> 3.4.0)
35 | rspec-expectations (~> 3.4.0)
36 | rspec-mocks (~> 3.4.0)
37 | rspec-core (3.4.1)
38 | rspec-support (~> 3.4.0)
39 | rspec-expectations (3.4.0)
40 | diff-lcs (>= 1.2.0, < 2.0)
41 | rspec-support (~> 3.4.0)
42 | rspec-mocks (3.4.1)
43 | diff-lcs (>= 1.2.0, < 2.0)
44 | rspec-support (~> 3.4.0)
45 | rspec-support (3.4.1)
46 | rspec_junit_formatter (0.2.3)
47 | builder (< 4)
48 | rspec-core (>= 2, < 4, != 2.12.0)
49 | signet (0.4.5)
50 | addressable (>= 2.2.3)
51 | faraday (~> 0.8.1)
52 | jwt (>= 0.1.5)
53 | multi_json (>= 1.0.0)
54 | thor (0.19.1)
55 | uuidtools (2.1.5)
56 |
57 | PLATFORMS
58 | ruby
59 |
60 | DEPENDENCIES
61 | google-api-client (= 0.6.4)
62 | jwt (~> 0.1.4)
63 | nokogiri
64 | rspec (>= 3.1)
65 | rspec_junit_formatter
66 | thor
67 |
68 | BUNDLED WITH
69 | 1.15.4
70 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | #===============================================================================
2 | # DEFAULT MAKE VARIABLES
3 | #===============================================================================
4 |
5 | # defaults to "Test doc for gd-pandoc"
6 | doc = https://docs.google.com/a/evolvingweb.ca/document/d/1dwYaiiy4P0KA7PvNwAP2fsPAf6qMMNzwaq8W66mwyds/edit#heading=h.4lk08p1hx3w
7 |
8 | outdir=build
9 | doc_id = $(shell echo $(doc) | sed -e 's@^https.*document/d/@@' -e 's@/edit.*@@')
10 | name = default
11 | input_file = input/$(name).html
12 | OUTPUT=$(outdir)/$(name)
13 | auth_file = google-api-authorization.yaml
14 | docker_workdir=/var/gdocs-export/
15 | docker_run_cmd = docker run -t -i -v `pwd`:$(docker_workdir) -p 12736:12736 dergachev/gdocs-export
16 |
17 | # directory containing customized header.tex, etc...
18 | theme = sample
19 |
20 | all: convert
21 |
22 | #===============================================================================
23 | # GOOGLE_DRIVE_API TARGETS
24 | #===============================================================================
25 |
26 | install_auth_file:
27 | cp $(workdir)$(auth_file) ~/.google-api.yaml
28 |
29 | api_auth:
30 | bundle exec ruby bin/authorize.rb \
31 | $(CLIENT_ID) $(CLIENT_SECRET) \
32 | https://www.googleapis.com/auth/drive.readonly \
33 | > $(auth_file)
34 |
35 | api_download: install_auth_file
36 | bundle exec google-api execute \
37 | -u "https://docs.google.com/feeds/download/documents/export/Export?id=$(doc_id)&exportFormat=html" \
38 | > $(input_file)
39 |
40 | #===============================================================================
41 | # PANDOC TARGETS
42 | #===============================================================================
43 |
44 | latex:
45 | mkdir -p $(OUTPUT)
46 | cp assets/default/* $(OUTPUT)
47 | test -z "$(theme)" || cp assets/$(theme)/* $(OUTPUT)
48 | cp $(input_file) $(OUTPUT)/in.html
49 |
50 | bundle exec ruby -C$(OUTPUT) "$$PWD/lib/pandoc-preprocess.rb" in.html > $(OUTPUT)/preprocessed.html
51 | pandoc --parse-raw $(OUTPUT)/preprocessed.html -t json > $(OUTPUT)/pre.json
52 | cat $(OUTPUT)/pre.json | ./lib/pandoc-filter.py > $(OUTPUT)/post.json
53 |
54 | # use pandoc to create metadata.tex, main.tex (these are included by ew-template.tex)
55 | pandoc $(OUTPUT)/post.json --no-wrap -t latex --template $(OUTPUT)/template-metadata.tex > $(OUTPUT)/metadata.tex
56 | pandoc $(OUTPUT)/post.json --chapters --no-wrap -t latex > $(OUTPUT)/main.tex
57 |
58 | # must use -o with docx output format, since its binary
59 | pandoc $(OUTPUT)/post.json -s -t docx -o $(OUTPUT)/$(name).docx
60 | pandoc $(OUTPUT)/post.json -s -t rtf -o $(OUTPUT)/$(name).rtf
61 |
62 | pdf:
63 | # convert latex to PDF
64 | echo "Created $(OUTPUT)/$(name).tex, compiling into $(name).pdf"
65 | # rubber will set output PDF filename based on latex input filename
66 | cp -f $(OUTPUT)/template.tex $(OUTPUT)/$(name).tex
67 | ( cd $(OUTPUT); latexmk -pdf $(name))
68 |
69 | convert: latex pdf
70 |
71 | diff:
72 | /usr/bin/perl "`which latexdiff`" --flatten $(outdir)/$(before)/$(before).tex $(OUTPUT)/$(name).tex > $(OUTPUT)/diff.tex
73 | (cd $(OUTPUT); latexmk -pdf diff)
74 |
75 |
76 | #===============================================================================
77 | # DOCKER TARGETS
78 | #===============================================================================
79 |
80 | build_docker:
81 | @echo "Warning: building can take a while (~15m)."
82 | dpkg -l squid-deb-proxy || sudo apt-get install -y squid-deb-proxy
83 | docker build -t dergachev/gdocs-export .
84 |
85 | docker_debug:
86 | $(docker_run_cmd) /bin/bash
87 |
88 | latest:
89 | docker run -t -i `docker images -q | head -n 1` /bin/bash
90 |
91 | docker_api_auth:
92 | $(docker_run_cmd) make api_auth CLIENT_ID=$(CLIENT_ID) CLIENT_SECRET=$(CLIENT_SECRET)
93 |
94 | docker_api_download:
95 | $(docker_run_cmd) make api_download doc_id=$(doc_id) input_file=$(input_file) workdir=$(docker_workdir)
96 |
97 | docker_convert:
98 | $(docker_run_cmd) make convert OUTPUT=$(OUTPUT) name=$(name) input_file=$(input_file) theme=$(theme)
99 |
100 | docker_diff:
101 | docker run -t -i -v `pwd`:$(docker_workdir) -p 12736:12736 dergachev/gdocs-export make diff OUTPUT=$(OUTPUT) name=$(name) input_file=$(input_file) before=$(before)
102 |
103 | #===============================================================================
104 | # MISC TARGETS
105 | #===============================================================================
106 |
107 | test:
108 | bundle exec rspec
109 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | gdocs-export
2 | ============
3 |
4 | Script to programatically download a text document from Google Docs and convert
5 | it to LaTeX, and compile it into a PDF.
6 |
7 | For example, this [google
8 | doc](https://docs.google.com/a/evolvingweb.ca/document/d/1dwYaiiy4P0KA7PvNwAP2fsPAf6qMMNzwaq8W66mwyds/edit)
9 | is converted into [this
10 | pdf](https://raw.githubusercontent.com/dergachev/gdocs-export/master/build/example/example.pdf).
11 |
12 | Under the hood, it uses [pandoc](http://johnmacfarlane.net/pandoc/) to convert from HTML to LaTeX and PDF.
13 |
14 | Installation
15 | ------------
16 |
17 | See below for how to get google drive API *client_id* and *client_secret*.
18 | See the `Vagrantfile` for installation steps.
19 | See the `Makefile` for usage.
20 |
21 | Starts a VM with docker and squid-deb-proxy running, then builds the gdocs-export docker image:
22 |
23 | ```bash
24 | vagrant up
25 | vagrant ssh
26 | cd /vagrant/
27 |
28 | # pulls the image from index.docker.io (about ~2GB)
29 | docker pull dergachev/gdocs-export
30 | ```
31 |
32 | Alternatively, we can build the image from this repo, but this takes a while
33 | and installing squid-deb-proxy to cache 'apt-get install' downloads is highly
34 | recommended:
35 |
36 | ```bash
37 | # optional, caches apt-get downloads in containers
38 | apt-get install -y squid-deb-proxy
39 |
40 | # takes 10-20 minutes
41 | docker build -t dergachev/gdocs-export .
42 | ```
43 |
44 | To run the tests, do the following:
45 |
46 | ```bash
47 | bundle config build.nokogiri --use-system-libraries
48 | bundle install
49 |
50 | make test
51 | ```
52 |
53 | Configuration
54 | -------------
55 |
56 | Before being able to interact with Google APIs, you'll need to register a new
57 | project in the [Google Developers
58 | Console](https://console.developers.google.com/project), enable Google Drive
59 | SDK for it, then retreive the associated *client_id* and *client_secret*
60 | parameters. For help with this, see below.
61 |
62 | ```bash
63 | # see below for how to get your Google API client_id and client_secret (these are fake)
64 | export CLIENT_ID=409429960585-o2i6nc17gf5sdhpa6o2g5gkkclmq229g.apps.googleusercontent.com
65 | export CLIENT_SECRET=PqKk00otoY11cxEfSSE7pCdw
66 | ```
67 |
68 | Now you'll need to give our newly-registered app permission to read all the Google Drive
69 | documents associated with your account, which is done via an in-browser OATH workflow.
70 |
71 | Before gdocs-export can download Google Drive documents associated with your
72 | account, you'll need to grant it the appropriate permissions.
73 |
74 |
75 | ```bash
76 | make docker_api_auth CLIENT_ID=$CLIENT_ID CLIENT_SECRET=$CLIENT_SECRET
77 | ```
78 |
79 | That command command will prompt you to visit a URL that looks like this:
80 |
81 | ```
82 | https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force&client_id=CLIENT_ID_GOES_HERE&redirect_uri=http://localhost:12736/&response_type=code&scope=https://www.googleapis.com/auth/drive.readonly
83 | ```
84 |
85 | On successful authorization, the browser will be automatically redirected to
86 | http://localhost:12736, where the command is listening for the resulting access
87 | tokens, which it will save to `./google-api-authorization.yaml` in the
88 | following format:
89 |
90 | ```
91 | ---
92 | mechanism: oauth_2
93 | scope: https://www.googleapis.com/auth/drive.readonly
94 | client_id: 409429960585-o2i6nc17gf5sdhpa6o2g5gkkclmq229g.apps.googleusercontent.com
95 | client_secret: PqKk00otoY11cxEfSSE7pCdw
96 | access_token: ya29.1.klsfj3kj3kj23k4jkkjsfkfjksdjfksdjfkjjiuiquiuwiue-324k234kj324GI
97 | refresh_token: 1/EJKERKJERKJ3jkkj34998889i9jkAAAAAAjjjjjjjzQ
98 | ```
99 |
100 | The *client_id* and *client_secret* properties are your application's API
101 | credentials, while the *access_token* and *refresh_token* are proof that your
102 | application has been authorized access to a given account's data. Be sure to
103 | keep this file private!
104 |
105 | Usage
106 | -----
107 |
108 | Now that we've got all the access tokens we need, we can use it to download an
109 | arbitrary document. For example, try downloading the [gdocs-export example
110 | document](https://docs.google.com/a/evolvingweb.ca/document/d/1dwYaiiy4P0KA7PvNwAP2fsPAf6qMMNzwaq8W66mwyds/edit)
111 | which I've shared publicly.
112 |
113 | ```bash
114 | export GOOGLE_DOC_URL=https://docs.google.com/a/evolvingweb.ca/document/d/1dwYaiiy4P0KA7PvNwAP2fsPAf6qMMNzwaq8W66mwyds/edit
115 | make docker_api_download name=example doc=$GOOGLE_DOC_URL
116 | ```
117 |
118 | The above just created `./input/example.html`. Let's convert it to PDF:
119 |
120 | ```bash
121 | make docker_convert name=example
122 | ```
123 |
124 | The above command creates the following files inside of `./build/example/`:
125 |
126 | example.pdf
127 | example.docx
128 | example.rtf
129 |
130 | By default it'll use the header.tex and logo image in `./assets/sample/`.
131 | To use the customized files under `./assets/ew/` instead, do the following:
132 |
133 | ```bash
134 | # pick up latex assets from ./assets/ew/ instead of ./assets/sample
135 | make docker_convert name=example theme=ew
136 | ```
137 |
138 | Finally, we also support generating a diff.pdf highlighting differences between
139 | the current document and a previously downloaded and compiled version. The
140 | workflow is as follows:
141 |
142 | ```bash
143 | make docker_api_download name=my-doc-v1 doc=$GOOGLE_DOC_URL
144 | make docker_convert name=my-doc-v1
145 |
146 | # make some changes to the document
147 | make docker_api_download name=my-doc-v2 doc=$GOOGLE_DOC_URL
148 | make docker_convert name=my-doc-v2
149 |
150 | # creates build/my-doc-v2/diff.pdf
151 | make docker_diff before=my-doc-v1 name=my-doc-v2
152 | ```
153 |
154 | Registering with Google Developers Console
155 | ------------------------------------------
156 |
157 | The following shows how to get a register your app (or rather, your instance of
158 | gdocs-export) and get a Google API *client_id* and *client_secret* tokens.
159 |
160 | The Google Developers Console API console seems to be always changing. The
161 | following steps were sufficient as of March 4, 2014.
162 |
163 | * Visit https://console.developers.google.com/project, create new project (pick a descriptive name and ID)
164 | 
165 | * In the new project, go to "APIs & Auths > APIs" and enable "Drive SDK".
166 | * Leave defaults, since they seem to include http://localhost (which covers http://localhost:12736)
167 | 
168 | * Visit "APIs & Auths > Credentials" and click "Create New Client ID"
169 | 
170 | * Select "Installed Application" and "Other" when prompted for application type.
171 | 
172 | * Copy "Client ID" and "Client Secret", store them somewhere for future sourcing.
173 | 
174 |
175 | Installing on Mac
176 | -----------------
177 |
178 | ```
179 | sudo tlmgr latex-diff
180 | sudo tlmgr install latexmk
181 | ```
182 |
183 | See http://mg.readthedocs.io/latexmk.html
184 |
--------------------------------------------------------------------------------
/Vagrantfile:
--------------------------------------------------------------------------------
1 | # -*- mode: ruby -*-
2 | # vi: set ft=ruby :
3 |
4 | Vagrant.configure("2") do |config|
5 | config.vm.box = "raring64"
6 | config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/raring/current/raring-server-cloudimg-amd64-vagrant-disk1.box"
7 | config.ssh.forward_agent = true
8 |
9 | # building pandoc needs a lot of RAM (at least 512, the more the better)
10 | config.vm.provider :virtualbox do |vb|
11 | vb.customize ["modifyvm", :id, "--memory", "2048"]
12 | vb.customize ["modifyvm", :id, "--cpus", "2"]
13 | end
14 |
15 | # required for lib/authorize.rb
16 | config.vm.network "forwarded_port", guest: 12736, host: 12736
17 |
18 | if defined? VagrantPlugins::Cachier
19 | config.cache.auto_detect = true
20 | end
21 |
22 | config.vm.provision "docker"
23 |
24 | config.vm.provision :shell, :inline => <<-EOT
25 | # our docker containers expect the host to have squid-deb-proxy
26 | apt-get install squid-deb-proxy -y
27 | apt-get update
28 |
29 | # standard utilities
30 | apt-get install -y build-essential vim curl git
31 | EOT
32 | end
33 |
34 |
--------------------------------------------------------------------------------
/assets/default/template-metadata.tex:
--------------------------------------------------------------------------------
1 | \newcommand{\mytitle}{$title$}
2 | \newcommand{\mysubtitle}{$subtitle$}
3 | \newcommand{\mybody}{$body$}
4 |
5 | $if(header)$
6 | \newcommand{\myheader}{$header$}
7 | $endif$
8 |
9 | $if(footer)$
10 | \newcommand{\myfooter}{$footer$}
11 | $endif$
12 |
13 | % TODO: support table of key-value metadata in google doc
14 |
--------------------------------------------------------------------------------
/assets/default/template.tex:
--------------------------------------------------------------------------------
1 | % Docs in http://www.tex.ac.uk/ctan/macros/latex/contrib/memoir/memman.pdf
2 |
3 | \documentclass[letterpaper,oldfontcommands,11pt,extrafontsizes,onecolumn,oneside,openboth,final,titlepage]{memoir}
4 | \usepackage[T1]{fontenc}
5 | \usepackage{lmodern}
6 | \usepackage{amssymb,amsmath}
7 | \usepackage{ifxetex,ifluatex}
8 | \usepackage{fixltx2e} % provides \textsubscript
9 | \usepackage[dvipsnames]{xcolor} % Allows the definition of hex colors
10 | % use microtype if available
11 | \IfFileExists{microtype.sty}{\usepackage{microtype}}{}
12 | % use upquote if available, for straight quotes in verbatim environments
13 | \IfFileExists{upquote.sty}{\usepackage{upquote}}{}
14 | \usepackage[utf8]{inputenc}
15 | \usepackage{eurosym}
16 | % used for printing google docs templates
17 | \usepackage{longtable}
18 | % used for printing google docs images
19 | \usepackage{graphicx}
20 | % to support underlining, see http://tex.stackexchange.com/questions/13377/how-can-i-underline-a-single-word-with-latex
21 | \usepackage[normalem]{ulem}
22 | % used for google docs links
23 | \usepackage[unicode=true]{hyperref}
24 |
25 | \input{metadata.tex}
26 |
27 | \hypersetup{breaklinks=true,
28 | bookmarks=true,
29 | pdfauthor=, %TODO: fix the author
30 | pdftitle=\mytitle,
31 | colorlinks=true,
32 | urlcolor=blue,
33 | linkcolor=magenta,
34 | pdfborder={0 0 0}}
35 |
36 | % reduce page margins to a reasonable size
37 | \setlrmarginsandblock{2.5cm}{2.5cm}{*}
38 | \setulmarginsandblock{2.5cm}{2.5cm}{*}
39 | \setlength{\footskip}{1.2cm}
40 | \checkandfixthelayout
41 |
42 | % customize header and footer
43 | \nouppercaseheads
44 | \makepagestyle{mystyle}
45 | \pagestyle{mystyle}
46 | \aliaspagestyle{chapter}{mystyle}
47 |
48 | \ifdefined\myheader
49 | \makeheadrule{mystyle}{\textwidth}{0.5pt}
50 | \makeoddhead{mystyle}{}{}{\color{Gray}\itshape\myheader}
51 | \fi
52 |
53 | \ifdefined\myfooter
54 | \makeoddfoot{mystyle}{\myfooter}{}{\thepage/\thelastpage}
55 | \else
56 | \makeoddfoot{mystyle}{}{}{\thepage/\thelastpage}
57 | \fi
58 | \makefootrule{mystyle}{\textwidth}{0.5pt}{0ex}
59 |
60 | \makeheadfootruleprefix{mystyle}{\color{Gray}}{\color{Gray}}
61 |
62 | \chapterstyle{komalike} % also consider article
63 |
64 | % We will generate all images so they have a width \maxwidth. This means
65 | % that they will get their normal width if they fit onto the page, but
66 | % are scaled down if they would overflow the margins.
67 | \makeatletter
68 | \def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth
69 | \else\Gin@nat@width\fi}
70 | \makeatother
71 | \let\Oldincludegraphics\includegraphics
72 | \renewcommand{\includegraphics}[1]{\Oldincludegraphics[width=\maxwidth]{#1}}
73 |
74 | % Uncomment to make embedded links print out HREF in footnotes, good for printing
75 | % \renewcommand{\href}[2]{#2\footnote{\url{#1}}}
76 |
77 | \setlength{\parindent}{0pt}
78 | \setlength{\parskip}{7pt plus 2pt minus 1pt}
79 | \setlength{\emergencystretch}{3em} % prevent overfull lines
80 |
81 | % Limits depth of automatic numbering of headings.
82 | % 0 => Only H1 (chapters), 1 => Up to H2 (sections), 2 => Up to H3 (subsections)
83 | \setcounter{secnumdepth}{2}
84 |
85 | % memoir specific variable that lowers the title by 200pt
86 | \setlength{\droptitle}{200pt}
87 |
88 | % customize size and alignment of title and subtitle
89 | \pretitle{\begin{flushleft}}
90 | \title{\bfseries \HUGE \mytitle \\ \vskip .5em \LARGE \mysubtitle}
91 | \posttitle{\par\vskip1em{\normalfont\normalsize\scshape \par\vfill}\end{flushleft}\vskip 0.5em}
92 |
93 | % TODO generate the author somehow
94 | % \author{}
95 |
96 | % Suppress printing todays date.
97 | % TODO: currently looks bad if not blank; make it look good and replace with todays date
98 | \date{}
99 |
100 | \begin{document}
101 |
102 | \input{header.tex}
103 |
104 | \setcounter{tocdepth}{1}
105 | \begin{KeepFromToc}
106 | \hypersetup{linkcolor=black}
107 | \tableofcontents
108 | \end{KeepFromToc}
109 | \pagebreak
110 |
111 | \input{main.tex}
112 |
113 | \end{document}
114 |
--------------------------------------------------------------------------------
/assets/ew/ew-logo-large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dergachev/gdocs-export/df691eb51646026de0269c2942814afc3dea190c/assets/ew/ew-logo-large.png
--------------------------------------------------------------------------------
/assets/ew/header.tex:
--------------------------------------------------------------------------------
1 | \begin{titlingpage}
2 | \maketitle
3 | \vfill
4 | \Oldincludegraphics[width=3in]{./ew-logo-large.png}
5 | \end{titlingpage}
6 |
7 | \pagebreak
8 |
9 | % suppress headers on this page
10 | \thispagestyle{empty}
11 |
12 | \emph{Prepared by} \textbf{Evolving Web Inc.} \\
13 | 300, rue du Saint-Sacrement --- Suite 222 \\
14 | Montreal, QC H2Y 1X4 \\
15 | phone: (514) 844-4930 \\
16 | fax: (514) 807-7499 \\
17 | \href{http://evolvingweb.ca}{http://evolvingweb.ca}
18 |
19 | \vfill
20 | \textbf{CONFIDENTIAL}:~The contents of this document are confidential
21 | and are intended exclusively for the prospective customer of Evolving
22 | Web designated above and its employees. Distribution of or sharing of
23 | this information with persons or entities for which it is not intended
24 | is prohibited, in any form, without the express written consent of
25 | Evolving Web.
26 | \pagebreak
27 |
--------------------------------------------------------------------------------
/assets/sample/UNlogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dergachev/gdocs-export/df691eb51646026de0269c2942814afc3dea190c/assets/sample/UNlogo.png
--------------------------------------------------------------------------------
/assets/sample/header.tex:
--------------------------------------------------------------------------------
1 | \begin{titlingpage}
2 | \maketitle
3 | \vfill
4 | \Oldincludegraphics[width=2.5in]{./UNlogo.png}
5 | \end{titlingpage}
6 |
7 | \pagebreak
8 |
9 | % suppress headers on this page
10 | \thispagestyle{empty}
11 |
12 | \textbf{Prepared by:}
13 |
14 | Vendor Inc.,\\49 Seven square\\Montreal, QC H0H 0H0\\phone: (514)
15 | 345-6789\\fax: (514) 345-6789\\http://vendor.com
16 |
17 | \textbf{}
18 |
19 | \textbf{Prepared for:}
20 |
21 | Client LLC,\\37 Prime Lane\\Montreal, QC H0H 0H0\\phone: (514)
22 | 345-6789\\fax: (514) 345-6789\\http://supplier.com
23 |
24 | \vfill
25 |
26 | CONFIDENTIAL: The contents of this document are confidential and are
27 | intended exclusively for the designated recipients. The contents of this page
28 | is defined in a file called header.tex. Other legal mumbo jumbo\ldots{}.
29 |
30 | \pagebreak
31 |
--------------------------------------------------------------------------------
/bin/authorize.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | OAUTH_SERVER_PORT = 12736
4 |
5 | require 'signet/oauth_2/client'
6 | require 'webrick'
7 | require 'yaml'
8 |
9 | ARGV.unshift('--help') if ARGV.empty?
10 |
11 | module Google
12 | class InstalledAppFlow
13 | def initialize(options)
14 | @port = options[:port] || 9292
15 | @authorization = Signet::OAuth2::Client.new({
16 | :authorization_uri => 'https://accounts.google.com/o/oauth2/auth',
17 | :token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
18 | :redirect_uri => "http://localhost:#{@port}/"}.update(options)
19 | )
20 | end
21 |
22 | def authorize
23 | auth = @authorization
24 |
25 | server = WEBrick::HTTPServer.new(
26 | :Port => @port,
27 | :BindAddress =>"0.0.0.0",
28 | :Logger => WEBrick::Log.new(STDERR, 0),
29 | :AccessLog => []
30 | )
31 | trap("INT") { server.shutdown }
32 |
33 | server.mount_proc '/' do |req, res|
34 | auth.code = req.query['code']
35 | if auth.code
36 | auth.fetch_access_token!
37 | end
38 | res.status = WEBrick::HTTPStatus::RC_ACCEPTED
39 | res.body = <<-HTML
40 |
41 |
42 |
49 |
50 | You may close this window.
51 |
52 | HTML
53 |
54 | server.stop
55 | end
56 |
57 | STDERR.puts "Visit the following URL to authorize:\n #{auth.authorization_uri.to_s}"
58 | server.start
59 | if @authorization.access_token
60 | return @authorization
61 | else
62 | return nil
63 | end
64 | end
65 | end
66 |
67 | class CLI
68 | def self.oauth_2_login(options)
69 | flow = Google::InstalledAppFlow.new(
70 | :port => OAUTH_SERVER_PORT,
71 | :client_id => options[:client_credential_key],
72 | :client_secret => options[:client_credential_secret],
73 | :scope => options[:scope]
74 | )
75 |
76 | oauth_client = flow.authorize
77 | if oauth_client
78 | config = {
79 | "mechanism" => "oauth_2",
80 | "scope" => options[:scope],
81 | "client_id" => oauth_client.client_id,
82 | "client_secret" => oauth_client.client_secret,
83 | "access_token" => oauth_client.access_token,
84 | "refresh_token" => oauth_client.refresh_token
85 | }
86 | # AUTHFILE_YAML_PATH = 'google-api-authorization.yaml'
87 | # config_file = File.expand_path(AUTHFILE_YAML_PATH)
88 | # open(config_file, 'w') { |file| file.write(YAML.dump(config)) }
89 | STDERR.puts "Success! Received the following credentials (also printing them to STDOUT):"
90 | STDERR.puts YAML.dump(config)
91 | STDOUT.puts YAML.dump(config)
92 |
93 | end
94 | exit(0)
95 | end
96 | end
97 | end
98 |
99 | Google::CLI::oauth_2_login({ :client_credential_key => ARGV[0], :client_credential_secret => ARGV[1], :scope => ARGV[2] })
100 |
--------------------------------------------------------------------------------
/build/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dergachev/gdocs-export/df691eb51646026de0269c2942814afc3dea190c/build/.gitkeep
--------------------------------------------------------------------------------
/build/example/SHbO80B3zYiLwN09wcE1jxTyLWaBsr7llKXblVz0EwCQsT2EsGznwPa4YwxgN3BwyMSEZhyazyYEoA5TNEBswNe6FJ2ZZsw9_E5R3YnzdCYFfcxjoR-rnHg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dergachev/gdocs-export/df691eb51646026de0269c2942814afc3dea190c/build/example/SHbO80B3zYiLwN09wcE1jxTyLWaBsr7llKXblVz0EwCQsT2EsGznwPa4YwxgN3BwyMSEZhyazyYEoA5TNEBswNe6FJ2ZZsw9_E5R3YnzdCYFfcxjoR-rnHg.jpg
--------------------------------------------------------------------------------
/build/example/UNlogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dergachev/gdocs-export/df691eb51646026de0269c2942814afc3dea190c/build/example/UNlogo.png
--------------------------------------------------------------------------------
/build/example/example.aux:
--------------------------------------------------------------------------------
1 | \relax
2 | \providecommand\hyper@newdestlabel[2]{}
3 | \providecommand*{\memsetcounter}[2]{}
4 | \providecommand\HyperFirstAtBeginDocument{\AtBeginDocument}
5 | \HyperFirstAtBeginDocument{\ifx\hyper@anchor\@undefined
6 | \global\let\oldcontentsline\contentsline
7 | \gdef\contentsline#1#2#3#4{\oldcontentsline{#1}{#2}{#3}}
8 | \global\let\oldnewlabel\newlabel
9 | \gdef\newlabel#1#2{\newlabelxx{#1}#2}
10 | \gdef\newlabelxx#1#2#3#4#5#6{\oldnewlabel{#1}{{#2}{#3}}}
11 | \AtEndDocument{\ifx\hyper@anchor\@undefined
12 | \let\contentsline\oldcontentsline
13 | \let\newlabel\oldnewlabel
14 | \fi}
15 | \fi}
16 | \global\let\hyper@last\relax
17 | \gdef\HyperFirstAtBeginDocument#1{#1}
18 | \providecommand\HyField@AuxAddToFields[1]{}
19 | \providecommand\HyField@AuxAddToCoFields[2]{}
20 | \@writefile{lof}{\addvspace {10pt}}
21 | \@writefile{lot}{\addvspace {10pt}}
22 | \@writefile{toc}{\contentsline {chapter}{\chapternumberline {1}Background}{3}{chapter.1}}
23 | \@writefile{lof}{\addvspace {10pt}}
24 | \@writefile{lot}{\addvspace {10pt}}
25 | \@writefile{toc}{\contentsline {chapter}{\chapternumberline {2}Proposed Solution}{4}{chapter.2}}
26 | \@writefile{toc}{\contentsline {section}{\numberline {2.1}Export component}{4}{section.2.1}}
27 | \@writefile{toc}{\contentsline {section}{\numberline {2.2}Import component}{4}{section.2.2}}
28 | \@writefile{toc}{\contentsline {section}{\numberline {2.3}Benefits of our solution}{4}{section.2.3}}
29 | \gdef \LT@i {\LT@entry
30 | {1}{227.84059pt}\LT@entry
31 | {1}{227.84059pt}}
32 | \@writefile{lof}{\addvspace {10pt}}
33 | \@writefile{lot}{\addvspace {10pt}}
34 | \@writefile{toc}{\contentsline {chapter}{\chapternumberline {3}Schedule and Budget}{6}{chapter.3}}
35 | \@writefile{toc}{\contentsline {section}{\numberline {3.1}Table}{6}{section.3.1}}
36 | \@writefile{toc}{\contentsline {section}{\numberline {3.2}Diagram showing how work is split}{7}{section.3.2}}
37 | \@writefile{lof}{\addvspace {10pt}}
38 | \@writefile{lot}{\addvspace {10pt}}
39 | \@writefile{toc}{\contentsline {chapter}{\chapternumberline {4}Terms and Conditions}{8}{chapter.4}}
40 | \@writefile{toc}{\contentsline {section}{\numberline {4.1}Staffing}{8}{section.4.1}}
41 | \@writefile{toc}{\contentsline {section}{\numberline {4.2}Relation to Other Agreements}{8}{section.4.2}}
42 | \@writefile{toc}{\contentsline {section}{\numberline {4.3}Language of Contract}{8}{section.4.3}}
43 | \@writefile{lof}{\addvspace {10pt}}
44 | \@writefile{lot}{\addvspace {10pt}}
45 | \@writefile{toc}{\contentsline {chapter}{\chapternumberline {5}Agreement}{9}{chapter.5}}
46 | \memsetcounter{lastsheet}{10}
47 | \memsetcounter{lastpage}{9}
48 |
--------------------------------------------------------------------------------
/build/example/example.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dergachev/gdocs-export/df691eb51646026de0269c2942814afc3dea190c/build/example/example.docx
--------------------------------------------------------------------------------
/build/example/example.out:
--------------------------------------------------------------------------------
1 | \BOOKMARK [0][-]{chapter.1}{\376\377\000B\000a\000c\000k\000g\000r\000o\000u\000n\000d}{}% 1
2 | \BOOKMARK [0][-]{chapter.2}{\376\377\000P\000r\000o\000p\000o\000s\000e\000d\000\040\000S\000o\000l\000u\000t\000i\000o\000n}{}% 2
3 | \BOOKMARK [1][-]{section.2.1}{\376\377\000E\000x\000p\000o\000r\000t\000\040\000c\000o\000m\000p\000o\000n\000e\000n\000t}{chapter.2}% 3
4 | \BOOKMARK [1][-]{section.2.2}{\376\377\000I\000m\000p\000o\000r\000t\000\040\000c\000o\000m\000p\000o\000n\000e\000n\000t}{chapter.2}% 4
5 | \BOOKMARK [1][-]{section.2.3}{\376\377\000B\000e\000n\000e\000f\000i\000t\000s\000\040\000o\000f\000\040\000o\000u\000r\000\040\000s\000o\000l\000u\000t\000i\000o\000n}{chapter.2}% 5
6 | \BOOKMARK [0][-]{chapter.3}{\376\377\000S\000c\000h\000e\000d\000u\000l\000e\000\040\000a\000n\000d\000\040\000B\000u\000d\000g\000e\000t}{}% 6
7 | \BOOKMARK [1][-]{section.3.1}{\376\377\000T\000a\000b\000l\000e}{chapter.3}% 7
8 | \BOOKMARK [1][-]{section.3.2}{\376\377\000D\000i\000a\000g\000r\000a\000m\000\040\000s\000h\000o\000w\000i\000n\000g\000\040\000h\000o\000w\000\040\000w\000o\000r\000k\000\040\000i\000s\000\040\000s\000p\000l\000i\000t}{chapter.3}% 8
9 | \BOOKMARK [0][-]{chapter.4}{\376\377\000T\000e\000r\000m\000s\000\040\000a\000n\000d\000\040\000C\000o\000n\000d\000i\000t\000i\000o\000n\000s}{}% 9
10 | \BOOKMARK [1][-]{section.4.1}{\376\377\000S\000t\000a\000f\000f\000i\000n\000g}{chapter.4}% 10
11 | \BOOKMARK [1][-]{section.4.2}{\376\377\000R\000e\000l\000a\000t\000i\000o\000n\000\040\000t\000o\000\040\000O\000t\000h\000e\000r\000\040\000A\000g\000r\000e\000e\000m\000e\000n\000t\000s}{chapter.4}% 11
12 | \BOOKMARK [1][-]{section.4.3}{\376\377\000L\000a\000n\000g\000u\000a\000g\000e\000\040\000o\000f\000\040\000C\000o\000n\000t\000r\000a\000c\000t}{chapter.4}% 12
13 | \BOOKMARK [0][-]{chapter.5}{\376\377\000A\000g\000r\000e\000e\000m\000e\000n\000t}{}% 13
14 |
--------------------------------------------------------------------------------
/build/example/example.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dergachev/gdocs-export/df691eb51646026de0269c2942814afc3dea190c/build/example/example.pdf
--------------------------------------------------------------------------------
/build/example/example.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\deff0{\fonttbl{\f0 \fswiss Helvetica;}{\f1 Courier;}}
2 | {\colortbl;\red255\green0\blue0;\red0\green0\blue255;}
3 | \widowctrl\hyphauto
4 |
5 | {\pard \qc \f0 \sa180 \li0 \fi0 \b \fs36 Sample Statement of Work\par}
6 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
7 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
8 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
9 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
10 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
11 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
12 | {\pard \ql \f0 \sa180 \li0 \fi0 \b \fs36 Background\par}
13 | {\pard \ql \f0 \sa180 \li0 \fi0 Every project needs a background. Here\u8217's some lorem ipsum about the project.\par}
14 | {\pard \ql \f0 \sa180 \li0 \fi0 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla sit amet arcu elit. Donec at massa et leo auctor fringilla vitae id erat. Nam feugiat velit vitae ornare tristique. Morbi porttitor, orci vel dapibus malesuada, magna libero ultrices massa, nec interdum diam nisi elementum ipsum. Vestibulum elit lorem, pretium id semper ac, aliquam ut dui. Etiam at magna non nibh viverra tristique. Maecenas consectetur vulputate tellus, ac rutrum felis molestie ut. Aenean tincidunt congue cursus. Maecenas in egestas magna, nec pellentesque justo. Phasellus ante quam, faucibus sit amet tellus et, fringilla convallis eros.\par}
15 | {\pard \ql \f0 \sa180 \li0 \fi0 Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.\par}
16 | {\pard \ql \f0 \sa180 \li0 \fi0 Nunc at odio massa. In hac habitasse platea dictumst. Donec posuere erat ac ultrices suscipit. Quisque id velit nulla. Ut in urna orci. Sed at euismod neque. Sed velit velit, fringilla vitae eleifend eget, auctor id mi.\par}
17 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
18 | {\pard \ql \f0 \sa180 \li0 \fi0 \b \fs36 Proposed Solution\par}
19 | {\pard \ql \f0 \sa180 \li0 \fi0 This wouldn\u8217't be a proposal unless it proposed something. We will re-architect the system into a series of robust robust components:\par}
20 | {\pard \ql \f0 \sa0 \li360 \fi-360 1.\tx360\tab export, a script to export the data from access, validate it, refactor it into a logical format, then output it in plain-text format.\par}
21 | {\pard \ql \f0 \sa0 \li360 \fi-360 2.\tx360\tab import, a simple, robust module to handle file uploads of the cleaned data, and efficiently import this data. It will do sufficient error handling to allow full automation.\sa180\par}
22 | {\pard \ql \f0 \sa180 \li0 \fi0 As we perform the refactoring, we will rewriting the code to ensure it\u8217's documented, easy to test, and easy to update as future needs are identified.\par}
23 | {\pard \ql \f0 \sa180 \li0 \fi0 \b \fs32 Export component\par}
24 | {\pard \ql \f0 \sa0 \li360 \fi-360 \bullet \tx360\tab This will be a standalone script responsible for extracting data from source database, and cleaning it up as appropriate.\par}
25 | {\pard \ql \f0 \sa0 \li360 \fi-360 \bullet \tx360\tab The output format is human readable JSON files, with a logical, easy to understand structure\par}
26 | {\pard \ql \f0 \sa0 \li360 \fi-360 \bullet \tx360\tab Easy to verify output through manual inspection of output\par}
27 | {\pard \ql \f0 \sa0 \li360 \fi-360 \bullet \tx360\tab Include comprehensive automated tests to ensure output is correct\sa180\par}
28 | {\pard \ql \f0 \sa180 \li0 \fi0 \b \fs32 Import component\par}
29 | {\pard \ql \f0 \sa0 \li360 \fi-360 \bullet \tx360\tab Allows non-technical staff to upload already validated JSON files, and sync the target database accordingly.\par}
30 | {\pard \ql \f0 \sa0 \li360 \fi-360 \bullet \tx360\tab Assumes that input data is relatively safe, as it\u8217's produced via the export component\par}
31 | {\pard \ql \f0 \sa0 \li360 \fi-360 \bullet \tx360\tab Will be optimized to perform quickly; typical runs will be completed within 60 seconds\par}
32 | {\pard \ql \f0 \sa0 \li360 \fi-360 \bullet \tx360\tab Improved validation and error handling\sa180\par}
33 | {\pard \ql \f0 \sa180 \li0 \fi0 \b \fs32 Benefits of our solution\par}
34 | {\pard \ql \f0 \sa180 \li0 \fi0 Here\u8217's some more {\b lorem ipsum}\u160?for you all. Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.\par}
35 | {\pard \ql \f0 \sa180 \li0 \fi0 Nunc at odio massa. In hac habitasse platea dictumst. Donec posuere erat ac ultrices suscipit. Quisque id velit nulla. Ut in urna orci. Sed at euismod neque. Sed velit velit, fringilla vitae eleifend eget, auctor id mi.\par}
36 | {\pard \ql \f0 \sa180 \li0 \fi0 Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.\par}
37 | {\pard \ql \f0 \sa180 \li0 \fi0 Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.\par}
38 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
39 | {\pard \ql \f0 \sa180 \li0 \fi0 \b \fs36 Schedule and Budget\par}
40 | {\pard \ql \f0 \sa180 \li0 \fi0 The work described in this mandate will take 300 hours to complete, to be performed within 6-8 weeks of the signing of this agreement.\par}
41 | {\pard \ql \f0 \sa180 \li0 \fi0 \b \fs32 Table\par}
42 | {\pard \ql \f0 \sa180 \li0 \fi0 Here is a schedule of the work:\par}
43 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
44 | {\pard \ql \f0 \sa180 \li0 \fi0 {\field{\*\fldinst{HYPERLINK "#"}}{\fldrslt{\ul
45 |
46 | }}}
47 | {\field{\*\fldinst{HYPERLINK "#"}}{\fldrslt{\ul
48 |
49 | }}}
50 | \par}
51 | {
52 | \trowd \trgaph120
53 | \cellx4320\cellx8640
54 | \trkeep\intbl
55 | {
56 | {\intbl {\pard \ql \f0 \sa180 \li0 \fi0 gdocs-export:author\par}
57 | \cell}
58 | {\intbl {\pard \ql \f0 \sa180 \li0 \fi0 Alex\par}
59 | \cell}
60 | }
61 | \intbl\row}
62 | {
63 | \trowd \trgaph120
64 | \cellx4320\cellx8640
65 | \trkeep\intbl
66 | {
67 | {\intbl {\pard \ql \f0 \sa180 \li0 \fi0 1\par}
68 | \cell}
69 | {\intbl {\pard \ql \f0 \sa180 \li0 \fi0 150\par}
70 | \cell}
71 | }
72 | \intbl\row}
73 | {
74 | \trowd \trgaph120
75 | \cellx4320\cellx8640
76 | \trkeep\intbl
77 | {
78 | {\intbl {\pard \ql \f0 \sa180 \li0 \fi0 2\par}
79 | \cell}
80 | {\intbl {\pard \ql \f0 \sa180 \li0 \fi0 150\par}
81 | \cell}
82 | }
83 | \intbl\row}
84 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
85 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
86 | {\pard \ql \f0 \sa180 \li0 \fi0 The following diagram should be on a separate page, to test page break:\par}
87 | {\pard \ql \f0 \sa180 \li0 \fi0 \b \fs32 Diagram showing how work is split\par}
88 | {\pard \ql \f0 \sa180 \li0 \fi0 Every document needs a diagram!\par}
89 | {\pard \ql \f0 \sa180 \li0 \fi0 {\cf1 [image: SHbO80B3zYiLwN09wcE1jxTyLWaBsr7llKXblVz0EwCQsT2EsGznwPa4YwxgN3BwyMSEZhyazyYEoA5TNEBswNe6FJ2ZZsw9_E5R3YnzdCYFfcxjoR-rnHg.jpg]\cf0}\par}
90 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
91 | {\pard \ql \f0 \sa180 \li0 \fi0 \b \fs36 Terms and Conditions\par}
92 | {\pard \ql \f0 \sa180 \li0 \fi0 \b \fs32 Staffing\par}
93 | {\pard \ql \f0 \sa180 \li0 \fi0 Appropriate staff will be used for the execution of this project.\par}
94 | {\pard \ql \f0 \sa180 \li0 \fi0 \b \fs32 Relation to Other Agreements\par}
95 | {\pard \ql \f0 \sa180 \li0 \fi0 This agreement is governed by the same terms and conditions as some other documents.\par}
96 | {\pard \ql \f0 \sa180 \li0 \fi0 \b \fs32 Language of Contract\par}
97 | {\pard \ql \f0 \sa180 \li0 \fi0 Les parties ont convenu de r\u233?diger cette entente en anglais. Parties have decided that this agreement be formulated in English.\par}
98 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
99 | {\pard \ql \f0 \sa180 \li0 \fi0 \b \fs36 Agreement\par}
100 | {\pard \ql \f0 \sa180 \li0 \fi0 This document is a contract, and contracts must be signed.\par}
101 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
102 | {\pard \ql \f0 \sa180 \li0 \fi0 Signed on behalf of {\b Client LLC}:\par}
103 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
104 | {\pard \ql \f0 \sa180 \li0 \fi0 \u160?\par}
105 | {\pard \ql \f0 \sa180 \li0 \fi0 \u160?\par}
106 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
107 | {\pard \ql \f0 \sa180 \li0 \fi0 Name and title\par}
108 | {\pard \ql \f0 \sa180 \li0 \fi0 \u160?\par}
109 | {\pard \ql \f0 \sa180 \li0 \fi0 \u160?\par}
110 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
111 | {\pard \ql \f0 \sa180 \li0 \fi0 Signature and date\par}
112 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
113 | {\pard \ql \f0 \sa180 \li0 \fi0 Signed by Adam Smith, President of {\b Vendor Inc.:}\par}
114 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
115 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
116 | {\pard \ql \f0 \sa180 \li0 \fi0 \par}
117 | {\pard \ql \f0 \sa180 \li0 \fi0 Signature and date\par}
118 | }
119 |
--------------------------------------------------------------------------------
/build/example/example.tex:
--------------------------------------------------------------------------------
1 | % Docs in http://www.tex.ac.uk/ctan/macros/latex/contrib/memoir/memman.pdf
2 |
3 | \input{metadata.tex}
4 |
5 | \documentclass[letterpaper,oldfontcommands,11pt,extrafontsizes,onecolumn,oneside,openboth,final,titlepage]{memoir}
6 | \usepackage[T1]{fontenc}
7 | \usepackage{lmodern}
8 | \usepackage{amssymb,amsmath}
9 | \usepackage{ifxetex,ifluatex}
10 | \usepackage{fixltx2e} % provides \textsubscript
11 |
12 | % reduce page margins to a reasonable size
13 | \setlrmarginsandblock{2.5cm}{2.5cm}{*}
14 | \setulmarginsandblock{2.5cm}{2.5cm}{*}
15 | \checkandfixthelayout
16 |
17 |
18 | % use microtype if available
19 | \IfFileExists{microtype.sty}{\usepackage{microtype}}{}
20 | % use upquote if available, for straight quotes in verbatim environments
21 | \IfFileExists{upquote.sty}{\usepackage{upquote}}{}
22 |
23 | \usepackage[utf8]{inputenc}
24 | \usepackage{eurosym}
25 |
26 | % http://tex.stackexchange.com/questions/37868/fancyhdr-and-memoir
27 | \let\footruleskip\undefined
28 |
29 | \usepackage{fancyhdr}
30 | \renewcommand{\headrulewidth}{0pt}
31 |
32 | % Chapter 2. Project Description ==> 2. Project Description
33 | \renewcommand{\chaptermark}[1]{\markboth{\thechapter.\ #1}{}}
34 |
35 | \fancypagestyle{plain}{ % used for pages that start new chapters
36 | \lhead{}
37 | \chead{}
38 | \rhead{}
39 | \cfoot{}
40 | \rfoot{\thepage/\thelastpage}
41 | }
42 |
43 | \fancypagestyle{gdocs}{
44 | \lhead{}
45 | \chead{}
46 | \rhead{\itshape{\nouppercase{\leftmark}}}
47 | \cfoot{}
48 | \rfoot{\thepage/\thelastpage}
49 | }
50 |
51 | \pagestyle{gdocs}
52 |
53 | \chapterstyle{komalike} % also consider article, tandh
54 |
55 |
56 | % used for printing google docs templates
57 | \usepackage{longtable}
58 | % used for printing google docs images
59 | \usepackage{graphicx}
60 | % We will generate all images so they have a width \maxwidth. This means
61 | % that they will get their normal width if they fit onto the page, but
62 | % are scaled down if they would overflow the margins.
63 | \makeatletter
64 | \def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth
65 | \else\Gin@nat@width\fi}
66 | \makeatother
67 | \let\Oldincludegraphics\includegraphics
68 | \renewcommand{\includegraphics}[1]{\Oldincludegraphics[width=\maxwidth]{#1}}
69 |
70 | % used for google docs links
71 | \usepackage[unicode=true]{hyperref}
72 | \hypersetup{breaklinks=true,
73 | bookmarks=true,
74 | pdfauthor=, %TODO: fix the author
75 | pdftitle=\mytitle,
76 | colorlinks=true,
77 | urlcolor=blue,
78 | linkcolor=magenta,
79 | pdfborder={0 0 0}}
80 |
81 | % Uncommnet to make embedded links print out HREF in footnotes, good for printing
82 | % \renewcommand{\href}[2]{#2\footnote{\url{#1}}}
83 |
84 | \setlength{\parindent}{0pt}
85 | \setlength{\parskip}{7pt plus 2pt minus 1pt}
86 | \setlength{\emergencystretch}{3em} % prevent overfull lines
87 |
88 | % Limits depth of automatic numbering of headings.
89 | % 0 => Only H1 (chapters), 1 => Up to H2 (sections), 2 => Up to H3 (subsections)
90 | \setcounter{secnumdepth}{2}
91 |
92 | % memoir specific variable that lowers the title by 200pt
93 | \setlength{\droptitle}{200pt}
94 |
95 | % customize size and alignment of title and subtitle
96 | \pretitle{\begin{flushleft}}
97 | \title{\bfseries \HUGE \mytitle \\ \vskip .5em \LARGE \mysubtitle}
98 | \posttitle{\par\vskip1em{\normalfont\normalsize\scshape \par\vfill}\end{flushleft}\vskip 0.5em}
99 |
100 | % TODO generate the author somehow
101 | % \author{}
102 |
103 | % Suppress printing todays date.
104 | % TODO: currently looks bad if not blank; make it look good and replace with todays date
105 | \date{}
106 |
107 | \begin{document}
108 |
109 | \input{header.tex}
110 |
111 | \setcounter{tocdepth}{1}
112 | \begin{KeepFromToc}
113 | \hypersetup{linkcolor=black}
114 | \tableofcontents
115 | \end{KeepFromToc}
116 | \pagebreak
117 |
118 | \input{main.tex}
119 |
120 | \end{document}
121 |
--------------------------------------------------------------------------------
/build/example/example.toc:
--------------------------------------------------------------------------------
1 | \contentsline {chapter}{\chapternumberline {1}Background}{3}{chapter.1}
2 | \contentsline {chapter}{\chapternumberline {2}Proposed Solution}{4}{chapter.2}
3 | \contentsline {section}{\numberline {2.1}Export component}{4}{section.2.1}
4 | \contentsline {section}{\numberline {2.2}Import component}{4}{section.2.2}
5 | \contentsline {section}{\numberline {2.3}Benefits of our solution}{4}{section.2.3}
6 | \contentsline {chapter}{\chapternumberline {3}Schedule and Budget}{6}{chapter.3}
7 | \contentsline {section}{\numberline {3.1}Table}{6}{section.3.1}
8 | \contentsline {section}{\numberline {3.2}Diagram showing how work is split}{7}{section.3.2}
9 | \contentsline {chapter}{\chapternumberline {4}Terms and Conditions}{8}{chapter.4}
10 | \contentsline {section}{\numberline {4.1}Staffing}{8}{section.4.1}
11 | \contentsline {section}{\numberline {4.2}Relation to Other Agreements}{8}{section.4.2}
12 | \contentsline {section}{\numberline {4.3}Language of Contract}{8}{section.4.3}
13 | \contentsline {chapter}{\chapternumberline {5}Agreement}{9}{chapter.5}
14 |
--------------------------------------------------------------------------------
/build/example/header.tex:
--------------------------------------------------------------------------------
1 | \begin{titlingpage}
2 | \maketitle
3 | \vfill
4 | \Oldincludegraphics[width=3in]{./UNlogo.png}
5 | \end{titlingpage}
6 |
7 | \pagebreak
8 |
9 | \textbf{Prepared by:}
10 |
11 | Vendor Inc.,\\49 Seven square\\Montreal, QC H0H 0H0\\phone: (514)
12 | 345-6789\\fax: (514) 345-6789\\http://vendor.com
13 |
14 | \textbf{}
15 |
16 | \textbf{Prepared for:}
17 |
18 | Client LLC,\\37 Prime Lane\\Montreal, QC H0H 0H0\\phone: (514)
19 | 345-6789\\fax: (514) 345-6789\\http://supplier.com
20 |
21 | \vfill
22 |
23 | CONFIDENTIAL: The contents of this document are confidential and are
24 | intended exclusively for the designated recipients. The contents of this page
25 | is defined in a file called sample-header.tex. Other legal mumbo jumbo\ldots{}.
26 |
27 | \pagebreak
28 |
--------------------------------------------------------------------------------
/build/example/in.html:
--------------------------------------------------------------------------------
1 | Test doc for gd-pandocSample Statement of Work
Every document needs a subtitle...
Background
Every project needs a background. Here’s some lorem ipsum about the project.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla sit amet arcu elit. Donec at massa et leo auctor fringilla vitae id erat. Nam feugiat velit vitae ornare tristique. Morbi porttitor, orci vel dapibus malesuada, magna libero ultrices massa, nec interdum diam nisi elementum ipsum. Vestibulum elit lorem, pretium id semper ac, aliquam ut dui. Etiam at magna non nibh viverra tristique. Maecenas consectetur vulputate tellus, ac rutrum felis molestie ut. Aenean tincidunt congue cursus. Maecenas in egestas magna, nec pellentesque justo. Phasellus ante quam, faucibus sit amet tellus et, fringilla convallis eros.
Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
Nunc at odio massa. In hac habitasse platea dictumst. Donec posuere erat ac ultrices suscipit. Quisque id velit nulla. Ut in urna orci. Sed at euismod neque. Sed velit velit, fringilla vitae eleifend eget, auctor id mi.
Proposed Solution
This wouldn’t be a proposal unless it proposed something. We will re-architect the system into a series of robust robust components:
- export, a script to export the data from access, validate it, refactor it into a logical format, then output it in plain-text format.
- import, a simple, robust module to handle file uploads of the cleaned data, and efficiently import this data. It will do sufficient error handling to allow full automation.
As we perform the refactoring, we will rewriting the code to ensure it’s documented, easy to test, and easy to update as future needs are identified.
Export component
- This will be a standalone script responsible for extracting data from source database, and cleaning it up as appropriate.
- The output format is human readable JSON files, with a logical, easy to understand structure
- Easy to verify output through manual inspection of output
- Include comprehensive automated tests to ensure output is correct
Import component
- Allows non-technical staff to upload already validated JSON files, and sync the target database accordingly.
- Assumes that input data is relatively safe, as it’s produced via the export component
- Will be optimized to perform quickly; typical runs will be completed within 60 seconds
- Improved validation and error handling
Benefits of our solution
Here’s some more lorem ipsum for you all. Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
Nunc at odio massa. In hac habitasse platea dictumst. Donec posuere erat ac ultrices suscipit. Quisque id velit nulla. Ut in urna orci. Sed at euismod neque. Sed velit velit, fringilla vitae eleifend eget, auctor id mi.
Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
Schedule and Budget
The work described in this mandate will take 300 hours to complete, to be performed within 6-8 weeks of the signing of this agreement.
Table
Here is a schedule of the work:
gdocs-export:author | Alex |
1 | 150 |
2 | 150 |
The following diagram should be on a separate page, to test page break:
Diagram showing how work is split
Every document needs a diagram!

Terms and Conditions
Staffing
Appropriate staff will be used for the execution of this project.
Relation to Other Agreements
This agreement is governed by the same terms and conditions as some other documents.
Language of Contract
Les parties ont convenu de rédiger cette entente en anglais. Parties have decided that this agreement be formulated in English.
Agreement
This document is a contract, and contracts must be signed.
Signed on behalf of Client LLC:
Name and title
Signature and date
Signed by Adam Smith, President of Vendor Inc.:
Signature and date
2 |
--------------------------------------------------------------------------------
/build/example/main.tex:
--------------------------------------------------------------------------------
1 | \pagebreak
2 |
3 | \hyperdef{}{}{\chapter{Background}}
4 |
5 | Every project needs a background. Here's some lorem ipsum about the project.
6 |
7 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla sit amet arcu elit. Donec at massa et leo auctor fringilla vitae id erat. Nam feugiat velit vitae ornare tristique. Morbi porttitor, orci vel dapibus malesuada, magna libero ultrices massa, nec interdum diam nisi elementum ipsum. Vestibulum elit lorem, pretium id semper ac, aliquam ut dui. Etiam at magna non nibh viverra tristique. Maecenas consectetur vulputate tellus, ac rutrum felis molestie ut. Aenean tincidunt congue cursus. Maecenas in egestas magna, nec pellentesque justo. Phasellus ante quam, faucibus sit amet tellus et, fringilla convallis eros.
8 |
9 | Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
10 |
11 | Nunc at odio massa. In hac habitasse platea dictumst. Donec posuere erat ac ultrices suscipit. Quisque id velit nulla. Ut in urna orci. Sed at euismod neque. Sed velit velit, fringilla vitae eleifend eget, auctor id mi.
12 |
13 | \pagebreak
14 |
15 | \hyperdef{}{}{\chapter{Proposed Solution}}
16 |
17 | This wouldn't be a proposal unless it proposed something. We will re-architect the system into a series of robust robust components:
18 |
19 | \begin{enumerate}
20 | \itemsep1pt\parskip0pt\parsep0pt
21 | \item
22 | export, a script to export the data from access, validate it, refactor it into a logical format, then output it in plain-text format.
23 | \item
24 | import, a simple, robust module to handle file uploads of the cleaned data, and efficiently import this data. It will do sufficient error handling to allow full automation.
25 | \end{enumerate}
26 |
27 | As we perform the refactoring, we will rewriting the code to ensure it's documented, easy to test, and easy to update as future needs are identified.
28 |
29 | \hyperdef{}{}{\section{Export component}}
30 |
31 | \begin{itemize}
32 | \itemsep1pt\parskip0pt\parsep0pt
33 | \item
34 | This will be a standalone script responsible for extracting data from source database, and cleaning it up as appropriate.
35 | \item
36 | The output format is human readable JSON files, with a logical, easy to understand structure
37 | \item
38 | Easy to verify output through manual inspection of output
39 | \item
40 | Include comprehensive automated tests to ensure output is correct
41 | \end{itemize}
42 |
43 | \hyperdef{}{}{\section{Import component}}
44 |
45 | \begin{itemize}
46 | \itemsep1pt\parskip0pt\parsep0pt
47 | \item
48 | Allows non-technical staff to upload already validated JSON files, and sync the target database accordingly.
49 | \item
50 | Assumes that input data is relatively safe, as it's produced via the export component
51 | \item
52 | Will be optimized to perform quickly; typical runs will be completed within 60 seconds
53 | \item
54 | Improved validation and error handling
55 | \end{itemize}
56 |
57 | \hyperdef{}{}{\section{Benefits of our solution}}
58 |
59 | Here's some more \textbf{lorem ipsum}~for you all. Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
60 |
61 | Nunc at odio massa. In hac habitasse platea dictumst. Donec posuere erat ac ultrices suscipit. Quisque id velit nulla. Ut in urna orci. Sed at euismod neque. Sed velit velit, fringilla vitae eleifend eget, auctor id mi.
62 |
63 | Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
64 |
65 | Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
66 |
67 | \pagebreak
68 |
69 | \hyperdef{}{}{\chapter{Schedule and Budget}}
70 |
71 | The work described in this mandate will take 300 hours to complete, to be performed within 6-8 weeks of the signing of this agreement.
72 |
73 | \hyperdef{}{}{\section{Table}}
74 |
75 | Here is a schedule of the work:
76 |
77 | \hyperref[]{}\hyperref[]{}
78 |
79 | \begin{longtable}[c]{@{}ll@{}}
80 | \toprule\addlinespace
81 | \begin{minipage}[t]{0.47\columnwidth}\raggedright
82 | gdocs-export:author
83 | \end{minipage} & \begin{minipage}[t]{0.47\columnwidth}\raggedright
84 | Alex
85 | \end{minipage}
86 | \\\addlinespace
87 | \begin{minipage}[t]{0.47\columnwidth}\raggedright
88 | 1
89 | \end{minipage} & \begin{minipage}[t]{0.47\columnwidth}\raggedright
90 | 150
91 | \end{minipage}
92 | \\\addlinespace
93 | \begin{minipage}[t]{0.47\columnwidth}\raggedright
94 | 2
95 | \end{minipage} & \begin{minipage}[t]{0.47\columnwidth}\raggedright
96 | 150
97 | \end{minipage}
98 | \\\addlinespace
99 | \bottomrule
100 | \end{longtable}
101 |
102 | The following diagram should be on a separate page, to test page break:
103 |
104 | \pagebreak
105 |
106 | \hyperdef{}{}{\section{Diagram showing how work is split}}
107 |
108 | Every document needs a diagram!
109 |
110 | \includegraphics{SHbO80B3zYiLwN09wcE1jxTyLWaBsr7llKXblVz0EwCQsT2EsGznwPa4YwxgN3BwyMSEZhyazyYEoA5TNEBswNe6FJ2ZZsw9_E5R3YnzdCYFfcxjoR-rnHg.jpg}
111 |
112 | \pagebreak
113 |
114 | \hyperdef{}{}{\chapter{Terms and Conditions}}
115 |
116 | \hyperdef{}{}{\section{Staffing}}
117 |
118 | Appropriate staff will be used for the execution of this project.
119 |
120 | \hyperdef{}{}{\section{Relation to Other Agreements}}
121 |
122 | This agreement is governed by the same terms and conditions as some other documents.
123 |
124 | \hyperdef{}{}{\section{Language of Contract}}
125 |
126 | Les parties ont convenu de rédiger cette entente en anglais. Parties have decided that this agreement be formulated in English.
127 |
128 | \pagebreak
129 |
130 | \hyperdef{}{}{\chapter{Agreement}}
131 |
132 | This document is a contract, and contracts must be signed.
133 |
134 | Signed on behalf of \textbf{Client LLC}:
135 |
136 | ~
137 |
138 | ~
139 |
140 | \hrulefill
141 |
142 | Name and title
143 |
144 | ~
145 |
146 | ~
147 |
148 | \hrulefill
149 |
150 | Signature and date
151 |
152 | Signed by Adam Smith, President of \textbf{Vendor Inc.:}
153 |
154 | \hrulefill
155 |
156 | Signature and date
157 |
--------------------------------------------------------------------------------
/build/example/metadata.tex:
--------------------------------------------------------------------------------
1 | \newcommand{\mytitle}{Sample Statement of Work}
2 | \newcommand{\mysubtitle}{Every document needs a subtitle...}
3 | \newcommand{\mybody}{\pagebreak
4 |
5 | \hyperdef{}{}{\section{Background}}
6 |
7 | Every project needs a background. Here's some lorem ipsum about the project.
8 |
9 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla sit amet arcu elit. Donec at massa et leo auctor fringilla vitae id erat. Nam feugiat velit vitae ornare tristique. Morbi porttitor, orci vel dapibus malesuada, magna libero ultrices massa, nec interdum diam nisi elementum ipsum. Vestibulum elit lorem, pretium id semper ac, aliquam ut dui. Etiam at magna non nibh viverra tristique. Maecenas consectetur vulputate tellus, ac rutrum felis molestie ut. Aenean tincidunt congue cursus. Maecenas in egestas magna, nec pellentesque justo. Phasellus ante quam, faucibus sit amet tellus et, fringilla convallis eros.
10 |
11 | Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
12 |
13 | Nunc at odio massa. In hac habitasse platea dictumst. Donec posuere erat ac ultrices suscipit. Quisque id velit nulla. Ut in urna orci. Sed at euismod neque. Sed velit velit, fringilla vitae eleifend eget, auctor id mi.
14 |
15 | \pagebreak
16 |
17 | \hyperdef{}{}{\section{Proposed Solution}}
18 |
19 | This wouldn't be a proposal unless it proposed something. We will re-architect the system into a series of robust robust components:
20 |
21 | \begin{enumerate}
22 | \itemsep1pt\parskip0pt\parsep0pt
23 | \item
24 | export, a script to export the data from access, validate it, refactor it into a logical format, then output it in plain-text format.
25 | \item
26 | import, a simple, robust module to handle file uploads of the cleaned data, and efficiently import this data. It will do sufficient error handling to allow full automation.
27 | \end{enumerate}
28 |
29 | As we perform the refactoring, we will rewriting the code to ensure it's documented, easy to test, and easy to update as future needs are identified.
30 |
31 | \hyperdef{}{}{\subsection{Export component}}
32 |
33 | \begin{itemize}
34 | \itemsep1pt\parskip0pt\parsep0pt
35 | \item
36 | This will be a standalone script responsible for extracting data from source database, and cleaning it up as appropriate.
37 | \item
38 | The output format is human readable JSON files, with a logical, easy to understand structure
39 | \item
40 | Easy to verify output through manual inspection of output
41 | \item
42 | Include comprehensive automated tests to ensure output is correct
43 | \end{itemize}
44 |
45 | \hyperdef{}{}{\subsection{Import component}}
46 |
47 | \begin{itemize}
48 | \itemsep1pt\parskip0pt\parsep0pt
49 | \item
50 | Allows non-technical staff to upload already validated JSON files, and sync the target database accordingly.
51 | \item
52 | Assumes that input data is relatively safe, as it's produced via the export component
53 | \item
54 | Will be optimized to perform quickly; typical runs will be completed within 60 seconds
55 | \item
56 | Improved validation and error handling
57 | \end{itemize}
58 |
59 | \hyperdef{}{}{\subsection{Benefits of our solution}}
60 |
61 | Here's some more \textbf{lorem ipsum}~for you all. Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
62 |
63 | Nunc at odio massa. In hac habitasse platea dictumst. Donec posuere erat ac ultrices suscipit. Quisque id velit nulla. Ut in urna orci. Sed at euismod neque. Sed velit velit, fringilla vitae eleifend eget, auctor id mi.
64 |
65 | Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
66 |
67 | Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
68 |
69 | \pagebreak
70 |
71 | \hyperdef{}{}{\section{Schedule and Budget}}
72 |
73 | The work described in this mandate will take 300 hours to complete, to be performed within 6-8 weeks of the signing of this agreement.
74 |
75 | \hyperdef{}{}{\subsection{Table}}
76 |
77 | Here is a schedule of the work:
78 |
79 | \hyperref[]{}\hyperref[]{}
80 |
81 | \begin{longtable}[c]{@{}ll@{}}
82 | \toprule\addlinespace
83 | \begin{minipage}[t]{0.47\columnwidth}\raggedright
84 | gdocs-export:author
85 | \end{minipage} & \begin{minipage}[t]{0.47\columnwidth}\raggedright
86 | Alex
87 | \end{minipage}
88 | \\\addlinespace
89 | \begin{minipage}[t]{0.47\columnwidth}\raggedright
90 | 1
91 | \end{minipage} & \begin{minipage}[t]{0.47\columnwidth}\raggedright
92 | 150
93 | \end{minipage}
94 | \\\addlinespace
95 | \begin{minipage}[t]{0.47\columnwidth}\raggedright
96 | 2
97 | \end{minipage} & \begin{minipage}[t]{0.47\columnwidth}\raggedright
98 | 150
99 | \end{minipage}
100 | \\\addlinespace
101 | \bottomrule
102 | \end{longtable}
103 |
104 | The following diagram should be on a separate page, to test page break:
105 |
106 | \pagebreak
107 |
108 | \hyperdef{}{}{\subsection{Diagram showing how work is split}}
109 |
110 | Every document needs a diagram!
111 |
112 | \includegraphics{SHbO80B3zYiLwN09wcE1jxTyLWaBsr7llKXblVz0EwCQsT2EsGznwPa4YwxgN3BwyMSEZhyazyYEoA5TNEBswNe6FJ2ZZsw9_E5R3YnzdCYFfcxjoR-rnHg.jpg}
113 |
114 | \pagebreak
115 |
116 | \hyperdef{}{}{\section{Terms and Conditions}}
117 |
118 | \hyperdef{}{}{\subsection{Staffing}}
119 |
120 | Appropriate staff will be used for the execution of this project.
121 |
122 | \hyperdef{}{}{\subsection{Relation to Other Agreements}}
123 |
124 | This agreement is governed by the same terms and conditions as some other documents.
125 |
126 | \hyperdef{}{}{\subsection{Language of Contract}}
127 |
128 | Les parties ont convenu de rédiger cette entente en anglais. Parties have decided that this agreement be formulated in English.
129 |
130 | \pagebreak
131 |
132 | \hyperdef{}{}{\section{Agreement}}
133 |
134 | This document is a contract, and contracts must be signed.
135 |
136 | Signed on behalf of \textbf{Client LLC}:
137 |
138 | ~
139 |
140 | ~
141 |
142 | \hrulefill
143 |
144 | Name and title
145 |
146 | ~
147 |
148 | ~
149 |
150 | \hrulefill
151 |
152 | Signature and date
153 |
154 | Signed by Adam Smith, President of \textbf{Vendor Inc.:}
155 |
156 | \hrulefill
157 |
158 | Signature and date}
159 |
160 |
161 | % TODO: support table of key-value metadata in google doc
162 |
--------------------------------------------------------------------------------
/build/example/pre.json:
--------------------------------------------------------------------------------
1 | [{"unMeta":{"title":{"t":"MetaInlines","c":[{"t":"Str","c":"Test"},{"t":"Space","c":[]},{"t":"Str","c":"doc"},{"t":"Space","c":[]},{"t":"Str","c":"for"},{"t":"Space","c":[]},{"t":"Str","c":"gd"},{"t":"Str","c":"-"},{"t":"Str","c":"pandoc"}]}}},[{"t":"Header","c":[1,["",["ew-pandoc-title"],[]],[{"t":"Str","c":"Sample"},{"t":"Space","c":[]},{"t":"Str","c":"Statement"},{"t":"Space","c":[]},{"t":"Str","c":"of"},{"t":"Space","c":[]},{"t":"Str","c":"Work"}]]},{"t":"Header","c":[1,["",["ew-pandoc-subtitle"],[]],[{"t":"Str","c":"Every"},{"t":"Space","c":[]},{"t":"Str","c":"document"},{"t":"Space","c":[]},{"t":"Str","c":"needs"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"subtitle"},{"t":"Str","c":"."},{"t":"Str","c":"."},{"t":"Str","c":"."}]]},{"t":"Para","c":[]},{"t":"Para","c":[]},{"t":"Para","c":[]},{"t":"Para","c":[]},{"t":"Header","c":[1,["",["ew-pandoc-pagebreak"],[]],[]]},{"t":"Para","c":[]},{"t":"Header","c":[1,["",["c1"],[]],[{"t":"Str","c":"Background"}]]},{"t":"Para","c":[{"t":"Str","c":"Every"},{"t":"Space","c":[]},{"t":"Str","c":"project"},{"t":"Space","c":[]},{"t":"Str","c":"needs"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"background"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Here"},{"t":"Str","c":"’"},{"t":"Str","c":"s"},{"t":"Space","c":[]},{"t":"Str","c":"some"},{"t":"Space","c":[]},{"t":"Str","c":"lorem"},{"t":"Space","c":[]},{"t":"Str","c":"ipsum"},{"t":"Space","c":[]},{"t":"Str","c":"about"},{"t":"Space","c":[]},{"t":"Str","c":"the"},{"t":"Space","c":[]},{"t":"Str","c":"project"},{"t":"Str","c":"."}]},{"t":"Para","c":[{"t":"Str","c":"Lorem"},{"t":"Space","c":[]},{"t":"Str","c":"ipsum"},{"t":"Space","c":[]},{"t":"Str","c":"dolor"},{"t":"Space","c":[]},{"t":"Str","c":"sit"},{"t":"Space","c":[]},{"t":"Str","c":"amet,"},{"t":"Space","c":[]},{"t":"Str","c":"consectetur"},{"t":"Space","c":[]},{"t":"Str","c":"adipiscing"},{"t":"Space","c":[]},{"t":"Str","c":"elit"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Nulla"},{"t":"Space","c":[]},{"t":"Str","c":"sit"},{"t":"Space","c":[]},{"t":"Str","c":"amet"},{"t":"Space","c":[]},{"t":"Str","c":"arcu"},{"t":"Space","c":[]},{"t":"Str","c":"elit"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Donec"},{"t":"Space","c":[]},{"t":"Str","c":"at"},{"t":"Space","c":[]},{"t":"Str","c":"massa"},{"t":"Space","c":[]},{"t":"Str","c":"et"},{"t":"Space","c":[]},{"t":"Str","c":"leo"},{"t":"Space","c":[]},{"t":"Str","c":"auctor"},{"t":"Space","c":[]},{"t":"Str","c":"fringilla"},{"t":"Space","c":[]},{"t":"Str","c":"vitae"},{"t":"Space","c":[]},{"t":"Str","c":"id"},{"t":"Space","c":[]},{"t":"Str","c":"erat"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Nam"},{"t":"Space","c":[]},{"t":"Str","c":"feugiat"},{"t":"Space","c":[]},{"t":"Str","c":"velit"},{"t":"Space","c":[]},{"t":"Str","c":"vitae"},{"t":"Space","c":[]},{"t":"Str","c":"ornare"},{"t":"Space","c":[]},{"t":"Str","c":"tristique"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Morbi"},{"t":"Space","c":[]},{"t":"Str","c":"porttitor,"},{"t":"Space","c":[]},{"t":"Str","c":"orci"},{"t":"Space","c":[]},{"t":"Str","c":"vel"},{"t":"Space","c":[]},{"t":"Str","c":"dapibus"},{"t":"Space","c":[]},{"t":"Str","c":"malesuada,"},{"t":"Space","c":[]},{"t":"Str","c":"magna"},{"t":"Space","c":[]},{"t":"Str","c":"libero"},{"t":"Space","c":[]},{"t":"Str","c":"ultrices"},{"t":"Space","c":[]},{"t":"Str","c":"massa,"},{"t":"Space","c":[]},{"t":"Str","c":"nec"},{"t":"Space","c":[]},{"t":"Str","c":"interdum"},{"t":"Space","c":[]},{"t":"Str","c":"diam"},{"t":"Space","c":[]},{"t":"Str","c":"nisi"},{"t":"Space","c":[]},{"t":"Str","c":"elementum"},{"t":"Space","c":[]},{"t":"Str","c":"ipsum"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Vestibulum"},{"t":"Space","c":[]},{"t":"Str","c":"elit"},{"t":"Space","c":[]},{"t":"Str","c":"lorem,"},{"t":"Space","c":[]},{"t":"Str","c":"pretium"},{"t":"Space","c":[]},{"t":"Str","c":"id"},{"t":"Space","c":[]},{"t":"Str","c":"semper"},{"t":"Space","c":[]},{"t":"Str","c":"ac,"},{"t":"Space","c":[]},{"t":"Str","c":"aliquam"},{"t":"Space","c":[]},{"t":"Str","c":"ut"},{"t":"Space","c":[]},{"t":"Str","c":"dui"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Etiam"},{"t":"Space","c":[]},{"t":"Str","c":"at"},{"t":"Space","c":[]},{"t":"Str","c":"magna"},{"t":"Space","c":[]},{"t":"Str","c":"non"},{"t":"Space","c":[]},{"t":"Str","c":"nibh"},{"t":"Space","c":[]},{"t":"Str","c":"viverra"},{"t":"Space","c":[]},{"t":"Str","c":"tristique"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Maecenas"},{"t":"Space","c":[]},{"t":"Str","c":"consectetur"},{"t":"Space","c":[]},{"t":"Str","c":"vulputate"},{"t":"Space","c":[]},{"t":"Str","c":"tellus,"},{"t":"Space","c":[]},{"t":"Str","c":"ac"},{"t":"Space","c":[]},{"t":"Str","c":"rutrum"},{"t":"Space","c":[]},{"t":"Str","c":"felis"},{"t":"Space","c":[]},{"t":"Str","c":"molestie"},{"t":"Space","c":[]},{"t":"Str","c":"ut"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Aenean"},{"t":"Space","c":[]},{"t":"Str","c":"tincidunt"},{"t":"Space","c":[]},{"t":"Str","c":"congue"},{"t":"Space","c":[]},{"t":"Str","c":"cursus"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Maecenas"},{"t":"Space","c":[]},{"t":"Str","c":"in"},{"t":"Space","c":[]},{"t":"Str","c":"egestas"},{"t":"Space","c":[]},{"t":"Str","c":"magna,"},{"t":"Space","c":[]},{"t":"Str","c":"nec"},{"t":"Space","c":[]},{"t":"Str","c":"pellentesque"},{"t":"Space","c":[]},{"t":"Str","c":"justo"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Phasellus"},{"t":"Space","c":[]},{"t":"Str","c":"ante"},{"t":"Space","c":[]},{"t":"Str","c":"quam,"},{"t":"Space","c":[]},{"t":"Str","c":"faucibus"},{"t":"Space","c":[]},{"t":"Str","c":"sit"},{"t":"Space","c":[]},{"t":"Str","c":"amet"},{"t":"Space","c":[]},{"t":"Str","c":"tellus"},{"t":"Space","c":[]},{"t":"Str","c":"et,"},{"t":"Space","c":[]},{"t":"Str","c":"fringilla"},{"t":"Space","c":[]},{"t":"Str","c":"convallis"},{"t":"Space","c":[]},{"t":"Str","c":"eros"},{"t":"Str","c":"."}]},{"t":"Para","c":[{"t":"Str","c":"Nullam"},{"t":"Space","c":[]},{"t":"Str","c":"vitae"},{"t":"Space","c":[]},{"t":"Str","c":"imperdiet"},{"t":"Space","c":[]},{"t":"Str","c":"enim"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Ut"},{"t":"Space","c":[]},{"t":"Str","c":"id"},{"t":"Space","c":[]},{"t":"Str","c":"ultrices"},{"t":"Space","c":[]},{"t":"Str","c":"leo"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"In"},{"t":"Space","c":[]},{"t":"Str","c":"dictum"},{"t":"Space","c":[]},{"t":"Str","c":"bibendum"},{"t":"Space","c":[]},{"t":"Str","c":"vestibulum"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Donec"},{"t":"Space","c":[]},{"t":"Str","c":"lobortis"},{"t":"Space","c":[]},{"t":"Str","c":"condimentum"},{"t":"Space","c":[]},{"t":"Str","c":"mauris"},{"t":"Space","c":[]},{"t":"Str","c":"ac"},{"t":"Space","c":[]},{"t":"Str","c":"interdum"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Duis"},{"t":"Space","c":[]},{"t":"Str","c":"bibendum"},{"t":"Space","c":[]},{"t":"Str","c":"molestie"},{"t":"Space","c":[]},{"t":"Str","c":"eleifend"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Nunc"},{"t":"Space","c":[]},{"t":"Str","c":"non"},{"t":"Space","c":[]},{"t":"Str","c":"neque"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"neque"},{"t":"Space","c":[]},{"t":"Str","c":"ornare"},{"t":"Space","c":[]},{"t":"Str","c":"euismod"},{"t":"Space","c":[]},{"t":"Str","c":"non"},{"t":"Space","c":[]},{"t":"Str","c":"ut"},{"t":"Space","c":[]},{"t":"Str","c":"sapien"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Morbi"},{"t":"Space","c":[]},{"t":"Str","c":"sed"},{"t":"Space","c":[]},{"t":"Str","c":"diam"},{"t":"Space","c":[]},{"t":"Str","c":"quis"},{"t":"Space","c":[]},{"t":"Str","c":"felis"},{"t":"Space","c":[]},{"t":"Str","c":"commodo"},{"t":"Space","c":[]},{"t":"Str","c":"vehicula"},{"t":"Space","c":[]},{"t":"Str","c":"ac"},{"t":"Space","c":[]},{"t":"Str","c":"et"},{"t":"Space","c":[]},{"t":"Str","c":"augue"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Maecenas"},{"t":"Space","c":[]},{"t":"Str","c":"pretium"},{"t":"Space","c":[]},{"t":"Str","c":"justo"},{"t":"Space","c":[]},{"t":"Str","c":"sit"},{"t":"Space","c":[]},{"t":"Str","c":"amet"},{"t":"Space","c":[]},{"t":"Str","c":"imperdiet"},{"t":"Space","c":[]},{"t":"Str","c":"pharetra"},{"t":"Str","c":"."}]},{"t":"Para","c":[{"t":"Str","c":"Nunc"},{"t":"Space","c":[]},{"t":"Str","c":"at"},{"t":"Space","c":[]},{"t":"Str","c":"odio"},{"t":"Space","c":[]},{"t":"Str","c":"massa"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"In"},{"t":"Space","c":[]},{"t":"Str","c":"hac"},{"t":"Space","c":[]},{"t":"Str","c":"habitasse"},{"t":"Space","c":[]},{"t":"Str","c":"platea"},{"t":"Space","c":[]},{"t":"Str","c":"dictumst"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Donec"},{"t":"Space","c":[]},{"t":"Str","c":"posuere"},{"t":"Space","c":[]},{"t":"Str","c":"erat"},{"t":"Space","c":[]},{"t":"Str","c":"ac"},{"t":"Space","c":[]},{"t":"Str","c":"ultrices"},{"t":"Space","c":[]},{"t":"Str","c":"suscipit"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Quisque"},{"t":"Space","c":[]},{"t":"Str","c":"id"},{"t":"Space","c":[]},{"t":"Str","c":"velit"},{"t":"Space","c":[]},{"t":"Str","c":"nulla"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Ut"},{"t":"Space","c":[]},{"t":"Str","c":"in"},{"t":"Space","c":[]},{"t":"Str","c":"urna"},{"t":"Space","c":[]},{"t":"Str","c":"orci"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Sed"},{"t":"Space","c":[]},{"t":"Str","c":"at"},{"t":"Space","c":[]},{"t":"Str","c":"euismod"},{"t":"Space","c":[]},{"t":"Str","c":"neque"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Sed"},{"t":"Space","c":[]},{"t":"Str","c":"velit"},{"t":"Space","c":[]},{"t":"Str","c":"velit,"},{"t":"Space","c":[]},{"t":"Str","c":"fringilla"},{"t":"Space","c":[]},{"t":"Str","c":"vitae"},{"t":"Space","c":[]},{"t":"Str","c":"eleifend"},{"t":"Space","c":[]},{"t":"Str","c":"eget,"},{"t":"Space","c":[]},{"t":"Str","c":"auctor"},{"t":"Space","c":[]},{"t":"Str","c":"id"},{"t":"Space","c":[]},{"t":"Str","c":"mi"},{"t":"Str","c":"."}]},{"t":"Header","c":[1,["",["ew-pandoc-pagebreak"],[]],[]]},{"t":"Para","c":[]},{"t":"Header","c":[1,["",["c1"],[]],[{"t":"Str","c":"Proposed"},{"t":"Space","c":[]},{"t":"Str","c":"Solution"}]]},{"t":"Para","c":[{"t":"Str","c":"This"},{"t":"Space","c":[]},{"t":"Str","c":"wouldn"},{"t":"Str","c":"’"},{"t":"Str","c":"t"},{"t":"Space","c":[]},{"t":"Str","c":"be"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"proposal"},{"t":"Space","c":[]},{"t":"Str","c":"unless"},{"t":"Space","c":[]},{"t":"Str","c":"it"},{"t":"Space","c":[]},{"t":"Str","c":"proposed"},{"t":"Space","c":[]},{"t":"Str","c":"something"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"We"},{"t":"Space","c":[]},{"t":"Str","c":"will"},{"t":"Space","c":[]},{"t":"Str","c":"re"},{"t":"Str","c":"-"},{"t":"Str","c":"architect"},{"t":"Space","c":[]},{"t":"Str","c":"the"},{"t":"Space","c":[]},{"t":"Str","c":"system"},{"t":"Space","c":[]},{"t":"Str","c":"into"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"series"},{"t":"Space","c":[]},{"t":"Str","c":"of"},{"t":"Space","c":[]},{"t":"Str","c":"robust"},{"t":"Space","c":[]},{"t":"Str","c":"robust"},{"t":"Space","c":[]},{"t":"Str","c":"components:"}]},{"t":"OrderedList","c":[[1,{"t":"DefaultStyle","c":[]},{"t":"DefaultDelim","c":[]}],[[{"t":"Plain","c":[{"t":"Str","c":"export,"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"script"},{"t":"Space","c":[]},{"t":"Str","c":"to"},{"t":"Space","c":[]},{"t":"Str","c":"export"},{"t":"Space","c":[]},{"t":"Str","c":"the"},{"t":"Space","c":[]},{"t":"Str","c":"data"},{"t":"Space","c":[]},{"t":"Str","c":"from"},{"t":"Space","c":[]},{"t":"Str","c":"access,"},{"t":"Space","c":[]},{"t":"Str","c":"validate"},{"t":"Space","c":[]},{"t":"Str","c":"it,"},{"t":"Space","c":[]},{"t":"Str","c":"refactor"},{"t":"Space","c":[]},{"t":"Str","c":"it"},{"t":"Space","c":[]},{"t":"Str","c":"into"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"logical"},{"t":"Space","c":[]},{"t":"Str","c":"format,"},{"t":"Space","c":[]},{"t":"Str","c":"then"},{"t":"Space","c":[]},{"t":"Str","c":"output"},{"t":"Space","c":[]},{"t":"Str","c":"it"},{"t":"Space","c":[]},{"t":"Str","c":"in"},{"t":"Space","c":[]},{"t":"Str","c":"plain"},{"t":"Str","c":"-"},{"t":"Str","c":"text"},{"t":"Space","c":[]},{"t":"Str","c":"format"},{"t":"Str","c":"."}]}],[{"t":"Plain","c":[{"t":"Str","c":"import,"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"simple,"},{"t":"Space","c":[]},{"t":"Str","c":"robust"},{"t":"Space","c":[]},{"t":"Str","c":"module"},{"t":"Space","c":[]},{"t":"Str","c":"to"},{"t":"Space","c":[]},{"t":"Str","c":"handle"},{"t":"Space","c":[]},{"t":"Str","c":"file"},{"t":"Space","c":[]},{"t":"Str","c":"uploads"},{"t":"Space","c":[]},{"t":"Str","c":"of"},{"t":"Space","c":[]},{"t":"Str","c":"the"},{"t":"Space","c":[]},{"t":"Str","c":"cleaned"},{"t":"Space","c":[]},{"t":"Str","c":"data,"},{"t":"Space","c":[]},{"t":"Str","c":"and"},{"t":"Space","c":[]},{"t":"Str","c":"efficiently"},{"t":"Space","c":[]},{"t":"Str","c":"import"},{"t":"Space","c":[]},{"t":"Str","c":"this"},{"t":"Space","c":[]},{"t":"Str","c":"data"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"It"},{"t":"Space","c":[]},{"t":"Str","c":"will"},{"t":"Space","c":[]},{"t":"Str","c":"do"},{"t":"Space","c":[]},{"t":"Str","c":"sufficient"},{"t":"Space","c":[]},{"t":"Str","c":"error"},{"t":"Space","c":[]},{"t":"Str","c":"handling"},{"t":"Space","c":[]},{"t":"Str","c":"to"},{"t":"Space","c":[]},{"t":"Str","c":"allow"},{"t":"Space","c":[]},{"t":"Str","c":"full"},{"t":"Space","c":[]},{"t":"Str","c":"automation"},{"t":"Str","c":"."}]}]]]},{"t":"Para","c":[{"t":"Str","c":"As"},{"t":"Space","c":[]},{"t":"Str","c":"we"},{"t":"Space","c":[]},{"t":"Str","c":"perform"},{"t":"Space","c":[]},{"t":"Str","c":"the"},{"t":"Space","c":[]},{"t":"Str","c":"refactoring,"},{"t":"Space","c":[]},{"t":"Str","c":"we"},{"t":"Space","c":[]},{"t":"Str","c":"will"},{"t":"Space","c":[]},{"t":"Str","c":"rewriting"},{"t":"Space","c":[]},{"t":"Str","c":"the"},{"t":"Space","c":[]},{"t":"Str","c":"code"},{"t":"Space","c":[]},{"t":"Str","c":"to"},{"t":"Space","c":[]},{"t":"Str","c":"ensure"},{"t":"Space","c":[]},{"t":"Str","c":"it"},{"t":"Str","c":"’"},{"t":"Str","c":"s"},{"t":"Space","c":[]},{"t":"Str","c":"documented,"},{"t":"Space","c":[]},{"t":"Str","c":"easy"},{"t":"Space","c":[]},{"t":"Str","c":"to"},{"t":"Space","c":[]},{"t":"Str","c":"test,"},{"t":"Space","c":[]},{"t":"Str","c":"and"},{"t":"Space","c":[]},{"t":"Str","c":"easy"},{"t":"Space","c":[]},{"t":"Str","c":"to"},{"t":"Space","c":[]},{"t":"Str","c":"update"},{"t":"Space","c":[]},{"t":"Str","c":"as"},{"t":"Space","c":[]},{"t":"Str","c":"future"},{"t":"Space","c":[]},{"t":"Str","c":"needs"},{"t":"Space","c":[]},{"t":"Str","c":"are"},{"t":"Space","c":[]},{"t":"Str","c":"identified"},{"t":"Str","c":"."}]},{"t":"Header","c":[2,["",["c1"],[]],[{"t":"Str","c":"Export"},{"t":"Space","c":[]},{"t":"Str","c":"component"}]]},{"t":"BulletList","c":[[{"t":"Plain","c":[{"t":"Str","c":"This"},{"t":"Space","c":[]},{"t":"Str","c":"will"},{"t":"Space","c":[]},{"t":"Str","c":"be"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"standalone"},{"t":"Space","c":[]},{"t":"Str","c":"script"},{"t":"Space","c":[]},{"t":"Str","c":"responsible"},{"t":"Space","c":[]},{"t":"Str","c":"for"},{"t":"Space","c":[]},{"t":"Str","c":"extracting"},{"t":"Space","c":[]},{"t":"Str","c":"data"},{"t":"Space","c":[]},{"t":"Str","c":"from"},{"t":"Space","c":[]},{"t":"Str","c":"source"},{"t":"Space","c":[]},{"t":"Str","c":"database,"},{"t":"Space","c":[]},{"t":"Str","c":"and"},{"t":"Space","c":[]},{"t":"Str","c":"cleaning"},{"t":"Space","c":[]},{"t":"Str","c":"it"},{"t":"Space","c":[]},{"t":"Str","c":"up"},{"t":"Space","c":[]},{"t":"Str","c":"as"},{"t":"Space","c":[]},{"t":"Str","c":"appropriate"},{"t":"Str","c":"."}]}],[{"t":"Plain","c":[{"t":"Str","c":"The"},{"t":"Space","c":[]},{"t":"Str","c":"output"},{"t":"Space","c":[]},{"t":"Str","c":"format"},{"t":"Space","c":[]},{"t":"Str","c":"is"},{"t":"Space","c":[]},{"t":"Str","c":"human"},{"t":"Space","c":[]},{"t":"Str","c":"readable"},{"t":"Space","c":[]},{"t":"Str","c":"JSON"},{"t":"Space","c":[]},{"t":"Str","c":"files,"},{"t":"Space","c":[]},{"t":"Str","c":"with"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"logical,"},{"t":"Space","c":[]},{"t":"Str","c":"easy"},{"t":"Space","c":[]},{"t":"Str","c":"to"},{"t":"Space","c":[]},{"t":"Str","c":"understand"},{"t":"Space","c":[]},{"t":"Str","c":"structure"}]}],[{"t":"Plain","c":[{"t":"Str","c":"Easy"},{"t":"Space","c":[]},{"t":"Str","c":"to"},{"t":"Space","c":[]},{"t":"Str","c":"verify"},{"t":"Space","c":[]},{"t":"Str","c":"output"},{"t":"Space","c":[]},{"t":"Str","c":"through"},{"t":"Space","c":[]},{"t":"Str","c":"manual"},{"t":"Space","c":[]},{"t":"Str","c":"inspection"},{"t":"Space","c":[]},{"t":"Str","c":"of"},{"t":"Space","c":[]},{"t":"Str","c":"output"}]}],[{"t":"Plain","c":[{"t":"Str","c":"Include"},{"t":"Space","c":[]},{"t":"Str","c":"comprehensive"},{"t":"Space","c":[]},{"t":"Str","c":"automated"},{"t":"Space","c":[]},{"t":"Str","c":"tests"},{"t":"Space","c":[]},{"t":"Str","c":"to"},{"t":"Space","c":[]},{"t":"Str","c":"ensure"},{"t":"Space","c":[]},{"t":"Str","c":"output"},{"t":"Space","c":[]},{"t":"Str","c":"is"},{"t":"Space","c":[]},{"t":"Str","c":"correct"}]}]]},{"t":"Header","c":[2,["",["c1"],[]],[{"t":"Str","c":"Import"},{"t":"Space","c":[]},{"t":"Str","c":"component"}]]},{"t":"BulletList","c":[[{"t":"Plain","c":[{"t":"Str","c":"Allows"},{"t":"Space","c":[]},{"t":"Str","c":"non"},{"t":"Str","c":"-"},{"t":"Str","c":"technical"},{"t":"Space","c":[]},{"t":"Str","c":"staff"},{"t":"Space","c":[]},{"t":"Str","c":"to"},{"t":"Space","c":[]},{"t":"Str","c":"upload"},{"t":"Space","c":[]},{"t":"Str","c":"already"},{"t":"Space","c":[]},{"t":"Str","c":"validated"},{"t":"Space","c":[]},{"t":"Str","c":"JSON"},{"t":"Space","c":[]},{"t":"Str","c":"files,"},{"t":"Space","c":[]},{"t":"Str","c":"and"},{"t":"Space","c":[]},{"t":"Str","c":"sync"},{"t":"Space","c":[]},{"t":"Str","c":"the"},{"t":"Space","c":[]},{"t":"Str","c":"target"},{"t":"Space","c":[]},{"t":"Str","c":"database"},{"t":"Space","c":[]},{"t":"Str","c":"accordingly"},{"t":"Str","c":"."}]}],[{"t":"Plain","c":[{"t":"Str","c":"Assumes"},{"t":"Space","c":[]},{"t":"Str","c":"that"},{"t":"Space","c":[]},{"t":"Str","c":"input"},{"t":"Space","c":[]},{"t":"Str","c":"data"},{"t":"Space","c":[]},{"t":"Str","c":"is"},{"t":"Space","c":[]},{"t":"Str","c":"relatively"},{"t":"Space","c":[]},{"t":"Str","c":"safe,"},{"t":"Space","c":[]},{"t":"Str","c":"as"},{"t":"Space","c":[]},{"t":"Str","c":"it"},{"t":"Str","c":"’"},{"t":"Str","c":"s"},{"t":"Space","c":[]},{"t":"Str","c":"produced"},{"t":"Space","c":[]},{"t":"Str","c":"via"},{"t":"Space","c":[]},{"t":"Str","c":"the"},{"t":"Space","c":[]},{"t":"Str","c":"export"},{"t":"Space","c":[]},{"t":"Str","c":"component"}]}],[{"t":"Plain","c":[{"t":"Str","c":"Will"},{"t":"Space","c":[]},{"t":"Str","c":"be"},{"t":"Space","c":[]},{"t":"Str","c":"optimized"},{"t":"Space","c":[]},{"t":"Str","c":"to"},{"t":"Space","c":[]},{"t":"Str","c":"perform"},{"t":"Space","c":[]},{"t":"Str","c":"quickly;"},{"t":"Space","c":[]},{"t":"Str","c":"typical"},{"t":"Space","c":[]},{"t":"Str","c":"runs"},{"t":"Space","c":[]},{"t":"Str","c":"will"},{"t":"Space","c":[]},{"t":"Str","c":"be"},{"t":"Space","c":[]},{"t":"Str","c":"completed"},{"t":"Space","c":[]},{"t":"Str","c":"within"},{"t":"Space","c":[]},{"t":"Str","c":"60"},{"t":"Space","c":[]},{"t":"Str","c":"seconds"}]}],[{"t":"Plain","c":[{"t":"Str","c":"Improved"},{"t":"Space","c":[]},{"t":"Str","c":"validation"},{"t":"Space","c":[]},{"t":"Str","c":"and"},{"t":"Space","c":[]},{"t":"Str","c":"error"},{"t":"Space","c":[]},{"t":"Str","c":"handling"}]}]]},{"t":"Header","c":[2,["",["c1"],[]],[{"t":"Str","c":"Benefits"},{"t":"Space","c":[]},{"t":"Str","c":"of"},{"t":"Space","c":[]},{"t":"Str","c":"our"},{"t":"Space","c":[]},{"t":"Str","c":"solution"}]]},{"t":"Para","c":[{"t":"Str","c":"Here"},{"t":"Str","c":"’"},{"t":"Str","c":"s"},{"t":"Space","c":[]},{"t":"Str","c":"some"},{"t":"Space","c":[]},{"t":"Str","c":"more"},{"t":"Space","c":[]},{"t":"Strong","c":[{"t":"Str","c":"lorem"},{"t":"Space","c":[]},{"t":"Str","c":"ipsum"}]},{"t":"Str","c":" for"},{"t":"Space","c":[]},{"t":"Str","c":"you"},{"t":"Space","c":[]},{"t":"Str","c":"all"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Nullam"},{"t":"Space","c":[]},{"t":"Str","c":"vitae"},{"t":"Space","c":[]},{"t":"Str","c":"imperdiet"},{"t":"Space","c":[]},{"t":"Str","c":"enim"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Ut"},{"t":"Space","c":[]},{"t":"Str","c":"id"},{"t":"Space","c":[]},{"t":"Str","c":"ultrices"},{"t":"Space","c":[]},{"t":"Str","c":"leo"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"In"},{"t":"Space","c":[]},{"t":"Str","c":"dictum"},{"t":"Space","c":[]},{"t":"Str","c":"bibendum"},{"t":"Space","c":[]},{"t":"Str","c":"vestibulum"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Donec"},{"t":"Space","c":[]},{"t":"Str","c":"lobortis"},{"t":"Space","c":[]},{"t":"Str","c":"condimentum"},{"t":"Space","c":[]},{"t":"Str","c":"mauris"},{"t":"Space","c":[]},{"t":"Str","c":"ac"},{"t":"Space","c":[]},{"t":"Str","c":"interdum"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Duis"},{"t":"Space","c":[]},{"t":"Str","c":"bibendum"},{"t":"Space","c":[]},{"t":"Str","c":"molestie"},{"t":"Space","c":[]},{"t":"Str","c":"eleifend"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Nunc"},{"t":"Space","c":[]},{"t":"Str","c":"non"},{"t":"Space","c":[]},{"t":"Str","c":"neque"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"neque"},{"t":"Space","c":[]},{"t":"Str","c":"ornare"},{"t":"Space","c":[]},{"t":"Str","c":"euismod"},{"t":"Space","c":[]},{"t":"Str","c":"non"},{"t":"Space","c":[]},{"t":"Str","c":"ut"},{"t":"Space","c":[]},{"t":"Str","c":"sapien"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Morbi"},{"t":"Space","c":[]},{"t":"Str","c":"sed"},{"t":"Space","c":[]},{"t":"Str","c":"diam"},{"t":"Space","c":[]},{"t":"Str","c":"quis"},{"t":"Space","c":[]},{"t":"Str","c":"felis"},{"t":"Space","c":[]},{"t":"Str","c":"commodo"},{"t":"Space","c":[]},{"t":"Str","c":"vehicula"},{"t":"Space","c":[]},{"t":"Str","c":"ac"},{"t":"Space","c":[]},{"t":"Str","c":"et"},{"t":"Space","c":[]},{"t":"Str","c":"augue"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Maecenas"},{"t":"Space","c":[]},{"t":"Str","c":"pretium"},{"t":"Space","c":[]},{"t":"Str","c":"justo"},{"t":"Space","c":[]},{"t":"Str","c":"sit"},{"t":"Space","c":[]},{"t":"Str","c":"amet"},{"t":"Space","c":[]},{"t":"Str","c":"imperdiet"},{"t":"Space","c":[]},{"t":"Str","c":"pharetra"},{"t":"Str","c":"."}]},{"t":"Para","c":[{"t":"Str","c":"Nunc"},{"t":"Space","c":[]},{"t":"Str","c":"at"},{"t":"Space","c":[]},{"t":"Str","c":"odio"},{"t":"Space","c":[]},{"t":"Str","c":"massa"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"In"},{"t":"Space","c":[]},{"t":"Str","c":"hac"},{"t":"Space","c":[]},{"t":"Str","c":"habitasse"},{"t":"Space","c":[]},{"t":"Str","c":"platea"},{"t":"Space","c":[]},{"t":"Str","c":"dictumst"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Donec"},{"t":"Space","c":[]},{"t":"Str","c":"posuere"},{"t":"Space","c":[]},{"t":"Str","c":"erat"},{"t":"Space","c":[]},{"t":"Str","c":"ac"},{"t":"Space","c":[]},{"t":"Str","c":"ultrices"},{"t":"Space","c":[]},{"t":"Str","c":"suscipit"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Quisque"},{"t":"Space","c":[]},{"t":"Str","c":"id"},{"t":"Space","c":[]},{"t":"Str","c":"velit"},{"t":"Space","c":[]},{"t":"Str","c":"nulla"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Ut"},{"t":"Space","c":[]},{"t":"Str","c":"in"},{"t":"Space","c":[]},{"t":"Str","c":"urna"},{"t":"Space","c":[]},{"t":"Str","c":"orci"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Sed"},{"t":"Space","c":[]},{"t":"Str","c":"at"},{"t":"Space","c":[]},{"t":"Str","c":"euismod"},{"t":"Space","c":[]},{"t":"Str","c":"neque"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Sed"},{"t":"Space","c":[]},{"t":"Str","c":"velit"},{"t":"Space","c":[]},{"t":"Str","c":"velit,"},{"t":"Space","c":[]},{"t":"Str","c":"fringilla"},{"t":"Space","c":[]},{"t":"Str","c":"vitae"},{"t":"Space","c":[]},{"t":"Str","c":"eleifend"},{"t":"Space","c":[]},{"t":"Str","c":"eget,"},{"t":"Space","c":[]},{"t":"Str","c":"auctor"},{"t":"Space","c":[]},{"t":"Str","c":"id"},{"t":"Space","c":[]},{"t":"Str","c":"mi"},{"t":"Str","c":"."}]},{"t":"Para","c":[{"t":"Str","c":"Nullam"},{"t":"Space","c":[]},{"t":"Str","c":"vitae"},{"t":"Space","c":[]},{"t":"Str","c":"imperdiet"},{"t":"Space","c":[]},{"t":"Str","c":"enim"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Ut"},{"t":"Space","c":[]},{"t":"Str","c":"id"},{"t":"Space","c":[]},{"t":"Str","c":"ultrices"},{"t":"Space","c":[]},{"t":"Str","c":"leo"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"In"},{"t":"Space","c":[]},{"t":"Str","c":"dictum"},{"t":"Space","c":[]},{"t":"Str","c":"bibendum"},{"t":"Space","c":[]},{"t":"Str","c":"vestibulum"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Donec"},{"t":"Space","c":[]},{"t":"Str","c":"lobortis"},{"t":"Space","c":[]},{"t":"Str","c":"condimentum"},{"t":"Space","c":[]},{"t":"Str","c":"mauris"},{"t":"Space","c":[]},{"t":"Str","c":"ac"},{"t":"Space","c":[]},{"t":"Str","c":"interdum"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Duis"},{"t":"Space","c":[]},{"t":"Str","c":"bibendum"},{"t":"Space","c":[]},{"t":"Str","c":"molestie"},{"t":"Space","c":[]},{"t":"Str","c":"eleifend"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Nunc"},{"t":"Space","c":[]},{"t":"Str","c":"non"},{"t":"Space","c":[]},{"t":"Str","c":"neque"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"neque"},{"t":"Space","c":[]},{"t":"Str","c":"ornare"},{"t":"Space","c":[]},{"t":"Str","c":"euismod"},{"t":"Space","c":[]},{"t":"Str","c":"non"},{"t":"Space","c":[]},{"t":"Str","c":"ut"},{"t":"Space","c":[]},{"t":"Str","c":"sapien"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Morbi"},{"t":"Space","c":[]},{"t":"Str","c":"sed"},{"t":"Space","c":[]},{"t":"Str","c":"diam"},{"t":"Space","c":[]},{"t":"Str","c":"quis"},{"t":"Space","c":[]},{"t":"Str","c":"felis"},{"t":"Space","c":[]},{"t":"Str","c":"commodo"},{"t":"Space","c":[]},{"t":"Str","c":"vehicula"},{"t":"Space","c":[]},{"t":"Str","c":"ac"},{"t":"Space","c":[]},{"t":"Str","c":"et"},{"t":"Space","c":[]},{"t":"Str","c":"augue"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Maecenas"},{"t":"Space","c":[]},{"t":"Str","c":"pretium"},{"t":"Space","c":[]},{"t":"Str","c":"justo"},{"t":"Space","c":[]},{"t":"Str","c":"sit"},{"t":"Space","c":[]},{"t":"Str","c":"amet"},{"t":"Space","c":[]},{"t":"Str","c":"imperdiet"},{"t":"Space","c":[]},{"t":"Str","c":"pharetra"},{"t":"Str","c":"."}]},{"t":"Para","c":[{"t":"Str","c":"Nullam"},{"t":"Space","c":[]},{"t":"Str","c":"vitae"},{"t":"Space","c":[]},{"t":"Str","c":"imperdiet"},{"t":"Space","c":[]},{"t":"Str","c":"enim"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Ut"},{"t":"Space","c":[]},{"t":"Str","c":"id"},{"t":"Space","c":[]},{"t":"Str","c":"ultrices"},{"t":"Space","c":[]},{"t":"Str","c":"leo"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"In"},{"t":"Space","c":[]},{"t":"Str","c":"dictum"},{"t":"Space","c":[]},{"t":"Str","c":"bibendum"},{"t":"Space","c":[]},{"t":"Str","c":"vestibulum"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Donec"},{"t":"Space","c":[]},{"t":"Str","c":"lobortis"},{"t":"Space","c":[]},{"t":"Str","c":"condimentum"},{"t":"Space","c":[]},{"t":"Str","c":"mauris"},{"t":"Space","c":[]},{"t":"Str","c":"ac"},{"t":"Space","c":[]},{"t":"Str","c":"interdum"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Duis"},{"t":"Space","c":[]},{"t":"Str","c":"bibendum"},{"t":"Space","c":[]},{"t":"Str","c":"molestie"},{"t":"Space","c":[]},{"t":"Str","c":"eleifend"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Nunc"},{"t":"Space","c":[]},{"t":"Str","c":"non"},{"t":"Space","c":[]},{"t":"Str","c":"neque"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"neque"},{"t":"Space","c":[]},{"t":"Str","c":"ornare"},{"t":"Space","c":[]},{"t":"Str","c":"euismod"},{"t":"Space","c":[]},{"t":"Str","c":"non"},{"t":"Space","c":[]},{"t":"Str","c":"ut"},{"t":"Space","c":[]},{"t":"Str","c":"sapien"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Morbi"},{"t":"Space","c":[]},{"t":"Str","c":"sed"},{"t":"Space","c":[]},{"t":"Str","c":"diam"},{"t":"Space","c":[]},{"t":"Str","c":"quis"},{"t":"Space","c":[]},{"t":"Str","c":"felis"},{"t":"Space","c":[]},{"t":"Str","c":"commodo"},{"t":"Space","c":[]},{"t":"Str","c":"vehicula"},{"t":"Space","c":[]},{"t":"Str","c":"ac"},{"t":"Space","c":[]},{"t":"Str","c":"et"},{"t":"Space","c":[]},{"t":"Str","c":"augue"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Maecenas"},{"t":"Space","c":[]},{"t":"Str","c":"pretium"},{"t":"Space","c":[]},{"t":"Str","c":"justo"},{"t":"Space","c":[]},{"t":"Str","c":"sit"},{"t":"Space","c":[]},{"t":"Str","c":"amet"},{"t":"Space","c":[]},{"t":"Str","c":"imperdiet"},{"t":"Space","c":[]},{"t":"Str","c":"pharetra"},{"t":"Str","c":"."}]},{"t":"Header","c":[1,["",["ew-pandoc-pagebreak"],[]],[]]},{"t":"Para","c":[]},{"t":"Header","c":[1,["",["c1"],[]],[{"t":"Str","c":"Schedule"},{"t":"Space","c":[]},{"t":"Str","c":"and"},{"t":"Space","c":[]},{"t":"Str","c":"Budget"}]]},{"t":"Para","c":[{"t":"Str","c":"The"},{"t":"Space","c":[]},{"t":"Str","c":"work"},{"t":"Space","c":[]},{"t":"Str","c":"described"},{"t":"Space","c":[]},{"t":"Str","c":"in"},{"t":"Space","c":[]},{"t":"Str","c":"this"},{"t":"Space","c":[]},{"t":"Str","c":"mandate"},{"t":"Space","c":[]},{"t":"Str","c":"will"},{"t":"Space","c":[]},{"t":"Str","c":"take"},{"t":"Space","c":[]},{"t":"Str","c":"300"},{"t":"Space","c":[]},{"t":"Str","c":"hours"},{"t":"Space","c":[]},{"t":"Str","c":"to"},{"t":"Space","c":[]},{"t":"Str","c":"complete,"},{"t":"Space","c":[]},{"t":"Str","c":"to"},{"t":"Space","c":[]},{"t":"Str","c":"be"},{"t":"Space","c":[]},{"t":"Str","c":"performed"},{"t":"Space","c":[]},{"t":"Str","c":"within"},{"t":"Space","c":[]},{"t":"Str","c":"6"},{"t":"Str","c":"-"},{"t":"Str","c":"8"},{"t":"Space","c":[]},{"t":"Str","c":"weeks"},{"t":"Space","c":[]},{"t":"Str","c":"of"},{"t":"Space","c":[]},{"t":"Str","c":"the"},{"t":"Space","c":[]},{"t":"Str","c":"signing"},{"t":"Space","c":[]},{"t":"Str","c":"of"},{"t":"Space","c":[]},{"t":"Str","c":"this"},{"t":"Space","c":[]},{"t":"Str","c":"agreement"},{"t":"Str","c":"."}]},{"t":"Header","c":[2,["",["c1"],[]],[{"t":"Str","c":"Table"}]]},{"t":"Para","c":[{"t":"Str","c":"Here"},{"t":"Space","c":[]},{"t":"Str","c":"is"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"schedule"},{"t":"Space","c":[]},{"t":"Str","c":"of"},{"t":"Space","c":[]},{"t":"Str","c":"the"},{"t":"Space","c":[]},{"t":"Str","c":"work:"}]},{"t":"Para","c":[]},{"t":"Para","c":[{"t":"Link","c":[[],["#",""]]},{"t":"Link","c":[[],["#",""]]}]},{"t":"Table","c":[[],[{"t":"AlignLeft","c":[]},{"t":"AlignLeft","c":[]}],[0.5,0.5],[],[[[{"t":"Para","c":[{"t":"Str","c":"gdocs"},{"t":"Str","c":"-"},{"t":"Str","c":"export:author"}]}],[{"t":"Para","c":[{"t":"Str","c":"Alex"}]}]],[[{"t":"Para","c":[{"t":"Str","c":"1"}]}],[{"t":"Para","c":[{"t":"Str","c":"150"}]}]],[[{"t":"Para","c":[{"t":"Str","c":"2"}]}],[{"t":"Para","c":[{"t":"Str","c":"150"}]}]]]]},{"t":"Para","c":[]},{"t":"Para","c":[{"t":"Str","c":"The"},{"t":"Space","c":[]},{"t":"Str","c":"following"},{"t":"Space","c":[]},{"t":"Str","c":"diagram"},{"t":"Space","c":[]},{"t":"Str","c":"should"},{"t":"Space","c":[]},{"t":"Str","c":"be"},{"t":"Space","c":[]},{"t":"Str","c":"on"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"separate"},{"t":"Space","c":[]},{"t":"Str","c":"page,"},{"t":"Space","c":[]},{"t":"Str","c":"to"},{"t":"Space","c":[]},{"t":"Str","c":"test"},{"t":"Space","c":[]},{"t":"Str","c":"page"},{"t":"Space","c":[]},{"t":"Str","c":"break:"}]},{"t":"Header","c":[1,["",["ew-pandoc-pagebreak"],[]],[]]},{"t":"Header","c":[2,["",["c1"],[]],[{"t":"Str","c":"Diagram"},{"t":"Space","c":[]},{"t":"Str","c":"showing"},{"t":"Space","c":[]},{"t":"Str","c":"how"},{"t":"Space","c":[]},{"t":"Str","c":"work"},{"t":"Space","c":[]},{"t":"Str","c":"is"},{"t":"Space","c":[]},{"t":"Str","c":"split"}]]},{"t":"Para","c":[{"t":"Str","c":"Every"},{"t":"Space","c":[]},{"t":"Str","c":"document"},{"t":"Space","c":[]},{"t":"Str","c":"needs"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"diagram!"}]},{"t":"Para","c":[{"t":"Image","c":[[],["SHbO80B3zYiLwN09wcE1jxTyLWaBsr7llKXblVz0EwCQsT2EsGznwPa4YwxgN3BwyMSEZhyazyYEoA5TNEBswNe6FJ2ZZsw9_E5R3YnzdCYFfcxjoR-rnHg.jpg",""]]}]},{"t":"Header","c":[1,["",["ew-pandoc-pagebreak"],[]],[]]},{"t":"Para","c":[]},{"t":"Header","c":[1,["",["c1"],[]],[{"t":"Str","c":"Terms"},{"t":"Space","c":[]},{"t":"Str","c":"and"},{"t":"Space","c":[]},{"t":"Str","c":"Conditions"}]]},{"t":"Header","c":[2,["",["c1"],[]],[{"t":"Str","c":"Staffing"}]]},{"t":"Para","c":[{"t":"Str","c":"Appropriate"},{"t":"Space","c":[]},{"t":"Str","c":"staff"},{"t":"Space","c":[]},{"t":"Str","c":"will"},{"t":"Space","c":[]},{"t":"Str","c":"be"},{"t":"Space","c":[]},{"t":"Str","c":"used"},{"t":"Space","c":[]},{"t":"Str","c":"for"},{"t":"Space","c":[]},{"t":"Str","c":"the"},{"t":"Space","c":[]},{"t":"Str","c":"execution"},{"t":"Space","c":[]},{"t":"Str","c":"of"},{"t":"Space","c":[]},{"t":"Str","c":"this"},{"t":"Space","c":[]},{"t":"Str","c":"project"},{"t":"Str","c":"."}]},{"t":"Header","c":[2,["",["c1"],[]],[{"t":"Str","c":"Relation"},{"t":"Space","c":[]},{"t":"Str","c":"to"},{"t":"Space","c":[]},{"t":"Str","c":"Other"},{"t":"Space","c":[]},{"t":"Str","c":"Agreements"}]]},{"t":"Para","c":[{"t":"Str","c":"This"},{"t":"Space","c":[]},{"t":"Str","c":"agreement"},{"t":"Space","c":[]},{"t":"Str","c":"is"},{"t":"Space","c":[]},{"t":"Str","c":"governed"},{"t":"Space","c":[]},{"t":"Str","c":"by"},{"t":"Space","c":[]},{"t":"Str","c":"the"},{"t":"Space","c":[]},{"t":"Str","c":"same"},{"t":"Space","c":[]},{"t":"Str","c":"terms"},{"t":"Space","c":[]},{"t":"Str","c":"and"},{"t":"Space","c":[]},{"t":"Str","c":"conditions"},{"t":"Space","c":[]},{"t":"Str","c":"as"},{"t":"Space","c":[]},{"t":"Str","c":"some"},{"t":"Space","c":[]},{"t":"Str","c":"other"},{"t":"Space","c":[]},{"t":"Str","c":"documents"},{"t":"Str","c":"."}]},{"t":"Header","c":[2,["",["c1"],[]],[{"t":"Str","c":"Language"},{"t":"Space","c":[]},{"t":"Str","c":"of"},{"t":"Space","c":[]},{"t":"Str","c":"Contract"}]]},{"t":"Para","c":[{"t":"Str","c":"Les"},{"t":"Space","c":[]},{"t":"Str","c":"parties"},{"t":"Space","c":[]},{"t":"Str","c":"ont"},{"t":"Space","c":[]},{"t":"Str","c":"convenu"},{"t":"Space","c":[]},{"t":"Str","c":"de"},{"t":"Space","c":[]},{"t":"Str","c":"rédiger"},{"t":"Space","c":[]},{"t":"Str","c":"cette"},{"t":"Space","c":[]},{"t":"Str","c":"entente"},{"t":"Space","c":[]},{"t":"Str","c":"en"},{"t":"Space","c":[]},{"t":"Str","c":"anglais"},{"t":"Str","c":"."},{"t":"Space","c":[]},{"t":"Str","c":"Parties"},{"t":"Space","c":[]},{"t":"Str","c":"have"},{"t":"Space","c":[]},{"t":"Str","c":"decided"},{"t":"Space","c":[]},{"t":"Str","c":"that"},{"t":"Space","c":[]},{"t":"Str","c":"this"},{"t":"Space","c":[]},{"t":"Str","c":"agreement"},{"t":"Space","c":[]},{"t":"Str","c":"be"},{"t":"Space","c":[]},{"t":"Str","c":"formulated"},{"t":"Space","c":[]},{"t":"Str","c":"in"},{"t":"Space","c":[]},{"t":"Str","c":"English"},{"t":"Str","c":"."}]},{"t":"Header","c":[1,["",["ew-pandoc-pagebreak"],[]],[]]},{"t":"Para","c":[]},{"t":"Header","c":[1,["",["c1","c11"],[]],[{"t":"Str","c":"Agreement"}]]},{"t":"Para","c":[{"t":"Str","c":"This"},{"t":"Space","c":[]},{"t":"Str","c":"document"},{"t":"Space","c":[]},{"t":"Str","c":"is"},{"t":"Space","c":[]},{"t":"Str","c":"a"},{"t":"Space","c":[]},{"t":"Str","c":"contract,"},{"t":"Space","c":[]},{"t":"Str","c":"and"},{"t":"Space","c":[]},{"t":"Str","c":"contracts"},{"t":"Space","c":[]},{"t":"Str","c":"must"},{"t":"Space","c":[]},{"t":"Str","c":"be"},{"t":"Space","c":[]},{"t":"Str","c":"signed"},{"t":"Str","c":"."}]},{"t":"Para","c":[]},{"t":"Para","c":[{"t":"Str","c":"Signed"},{"t":"Space","c":[]},{"t":"Str","c":"on"},{"t":"Space","c":[]},{"t":"Str","c":"behalf"},{"t":"Space","c":[]},{"t":"Str","c":"of"},{"t":"Space","c":[]},{"t":"Strong","c":[{"t":"Str","c":"Client"},{"t":"Space","c":[]},{"t":"Str","c":"LLC"}]},{"t":"Str","c":":"}]},{"t":"Para","c":[]},{"t":"Para","c":[{"t":"Str","c":" "}]},{"t":"Para","c":[{"t":"Str","c":" "}]},{"t":"HorizontalRule","c":[]},{"t":"Para","c":[]},{"t":"Para","c":[{"t":"Str","c":"Name"},{"t":"Space","c":[]},{"t":"Str","c":"and"},{"t":"Space","c":[]},{"t":"Str","c":"title"}]},{"t":"Para","c":[{"t":"Str","c":" "}]},{"t":"Para","c":[{"t":"Str","c":" "}]},{"t":"HorizontalRule","c":[]},{"t":"Para","c":[]},{"t":"Para","c":[{"t":"Str","c":"Signature"},{"t":"Space","c":[]},{"t":"Str","c":"and"},{"t":"Space","c":[]},{"t":"Str","c":"date"}]},{"t":"Para","c":[]},{"t":"Para","c":[{"t":"Str","c":"Signed"},{"t":"Space","c":[]},{"t":"Str","c":"by"},{"t":"Space","c":[]},{"t":"Str","c":"Adam"},{"t":"Space","c":[]},{"t":"Str","c":"Smith,"},{"t":"Space","c":[]},{"t":"Str","c":"President"},{"t":"Space","c":[]},{"t":"Str","c":"of"},{"t":"Space","c":[]},{"t":"Strong","c":[{"t":"Str","c":"Vendor"},{"t":"Space","c":[]},{"t":"Str","c":"Inc"},{"t":"Str","c":"."},{"t":"Str","c":":"}]}]},{"t":"Para","c":[]},{"t":"Para","c":[]},{"t":"HorizontalRule","c":[]},{"t":"Para","c":[]},{"t":"Para","c":[{"t":"Str","c":"Signature"},{"t":"Space","c":[]},{"t":"Str","c":"and"},{"t":"Space","c":[]},{"t":"Str","c":"date"}]}]]
2 |
--------------------------------------------------------------------------------
/build/example/preprocessed.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Test doc for gd-pandoc
5 |
6 |
7 |
8 |
9 | Sample Statement of Work
10 | Every document needs a subtitle...
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Background
19 |
20 | Every project needs a background. Here’s some lorem ipsum about the project.
21 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla sit amet arcu elit. Donec at massa et leo auctor fringilla vitae id erat. Nam feugiat velit vitae ornare tristique. Morbi porttitor, orci vel dapibus malesuada, magna libero ultrices massa, nec interdum diam nisi elementum ipsum. Vestibulum elit lorem, pretium id semper ac, aliquam ut dui. Etiam at magna non nibh viverra tristique. Maecenas consectetur vulputate tellus, ac rutrum felis molestie ut. Aenean tincidunt congue cursus. Maecenas in egestas magna, nec pellentesque justo. Phasellus ante quam, faucibus sit amet tellus et, fringilla convallis eros.
22 | Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
23 | Nunc at odio massa. In hac habitasse platea dictumst. Donec posuere erat ac ultrices suscipit. Quisque id velit nulla. Ut in urna orci. Sed at euismod neque. Sed velit velit, fringilla vitae eleifend eget, auctor id mi.
24 |
25 |
26 |
27 | Proposed Solution
28 |
29 | This wouldn’t be a proposal unless it proposed something. We will re-architect the system into a series of robust robust components:
30 |
31 | - export, a script to export the data from access, validate it, refactor it into a logical format, then output it in plain-text format.
32 | - import, a simple, robust module to handle file uploads of the cleaned data, and efficiently import this data. It will do sufficient error handling to allow full automation.
33 |
34 | As we perform the refactoring, we will rewriting the code to ensure it’s documented, easy to test, and easy to update as future needs are identified.
35 |
36 | Export component
37 |
38 |
39 | - This will be a standalone script responsible for extracting data from source database, and cleaning it up as appropriate.
40 | - The output format is human readable JSON files, with a logical, easy to understand structure
41 | - Easy to verify output through manual inspection of output
42 | - Include comprehensive automated tests to ensure output is correct
43 |
44 |
45 | Import component
46 |
47 |
48 | - Allows non-technical staff to upload already validated JSON files, and sync the target database accordingly.
49 | - Assumes that input data is relatively safe, as it’s produced via the export component
50 | - Will be optimized to perform quickly; typical runs will be completed within 60 seconds
51 | - Improved validation and error handling
52 |
53 |
54 | Benefits of our solution
55 |
56 | Here’s some more lorem ipsum for you all. Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
57 | Nunc at odio massa. In hac habitasse platea dictumst. Donec posuere erat ac ultrices suscipit. Quisque id velit nulla. Ut in urna orci. Sed at euismod neque. Sed velit velit, fringilla vitae eleifend eget, auctor id mi.
58 | Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
59 | Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
60 |
61 |
62 |
63 | Schedule and Budget
64 |
65 | The work described in this mandate will take 300 hours to complete, to be performed within 6-8 weeks of the signing of this agreement.
66 |
67 | Table
68 |
69 | Here is a schedule of the work:
70 |
71 |
72 |
73 | gdocs-export:author |
74 | Alex |
75 |
76 |
77 | 1 |
78 | 150 |
79 |
80 |
81 | 2 |
82 | 150 |
83 |
84 |
85 |
86 | The following diagram should be on a separate page, to test page break:
87 |
88 |
89 | Diagram showing how work is split
90 |
91 | Every document needs a diagram!
92 | 
93 |
94 |
95 |
96 | Terms and Conditions
97 |
98 |
99 | Staffing
100 |
101 | Appropriate staff will be used for the execution of this project.
102 |
103 | Relation to Other Agreements
104 |
105 | This agreement is governed by the same terms and conditions as some other documents.
106 |
107 | Language of Contract
108 |
109 | Les parties ont convenu de rédiger cette entente en anglais. Parties have decided that this agreement be formulated in English.
110 |
111 |
112 |
113 | Agreement
114 |
115 | This document is a contract, and contracts must be signed.
116 |
117 | Signed on behalf of Client LLC:
118 |
119 |
120 |
121 |
122 |
123 | Name and title
124 |
125 |
126 |
127 |
128 | Signature and date
129 |
130 | Signed by Adam Smith, President of Vendor Inc.:
131 |
132 |
133 |
134 |
135 | Signature and date
136 |
137 |
138 |
--------------------------------------------------------------------------------
/build/example/template-metadata.tex:
--------------------------------------------------------------------------------
1 | \newcommand{\mytitle}{$title$}
2 | \newcommand{\mysubtitle}{$subtitle$}
3 | \newcommand{\mybody}{$body$}
4 |
5 |
6 | % TODO: support table of key-value metadata in google doc
7 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | test:
2 | override:
3 | - bundle exec rspec -r rspec_junit_formatter --format RspecJunitFormatter -o $CIRCLE_TEST_REPORTS/rspec/junit.xml
4 |
--------------------------------------------------------------------------------
/docker/libicu52_52.1-3_amd64.deb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dergachev/gdocs-export/df691eb51646026de0269c2942814afc3dea190c/docker/libicu52_52.1-3_amd64.deb
--------------------------------------------------------------------------------
/docker/pandoc-data_1.12.2.1-1build2_all.deb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dergachev/gdocs-export/df691eb51646026de0269c2942814afc3dea190c/docker/pandoc-data_1.12.2.1-1build2_all.deb
--------------------------------------------------------------------------------
/docker/pandoc_1.12.2.1-1build2_amd64.deb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dergachev/gdocs-export/df691eb51646026de0269c2942814afc3dea190c/docker/pandoc_1.12.2.1-1build2_amd64.deb
--------------------------------------------------------------------------------
/input/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dergachev/gdocs-export/df691eb51646026de0269c2942814afc3dea190c/input/.gitkeep
--------------------------------------------------------------------------------
/lib/include/preprocess.rb:
--------------------------------------------------------------------------------
1 | require 'nokogiri'
2 | require 'open-uri'
3 |
4 | class PandocPreprocess
5 | attr_reader :doc, :downloads
6 | def initialize(html)
7 | @source = html
8 | @doc = Nokogiri::HTML(html)
9 | doc.encoding = 'UTF-8'
10 | @downloads = {}
11 | end
12 |
13 | def download_resources
14 | @downloads.each do |path, src|
15 | open(path, 'w') { |f| open(src) { |img| f.write(img.read) }}
16 | end
17 | end
18 |
19 | def html
20 | @doc.to_html
21 | end
22 |
23 | def process
24 | validate
25 | fixup_image_paths
26 | fixup_image_parents
27 | fixup_titles
28 | fixup_span_styles
29 | fixup_headers_footers
30 | fixup_empty_headers
31 | fixup_page_breaks
32 | fixup_lists
33 | end
34 |
35 | # Replace remote with local images
36 | # All image srcs have absolute URLs
37 | def fixup_image_paths
38 | doc.css("img").each do |x|
39 | uri = x['src']
40 | name = File.basename(uri)
41 | name_with_ext = "#{name}.jpg"
42 | @downloads[name_with_ext] = uri
43 | x['src'] = name_with_ext
44 | end
45 | end
46 |
47 | # Sometimes images are placed inside a heading tag, which crashes latex
48 | def fixup_image_parents
49 | doc.css('h1,h2,h3,h4,h5,h6').>('img').each do |x|
50 | x.parent.replace(x)
51 | end
52 | end
53 |
54 | # Support Google Docs title format, this prepares it for extract_metadata
55 | def fixup_titles
56 | # TODO: ensure neither title or subtitle occur more than once, or are empty
57 | %w[title subtitle].each do |type|
58 | doc.css("p.#{type}").each do |x|
59 | x.replace("#{x.text}
")
60 | end
61 | end
62 | end
63 |
64 | def fixup_span_styles
65 | # Source has, eg:
66 | # .c14{font-weight:bold}
67 | # Bold Text
68 | #
69 | # Because pandoc doesn't support , we make it into h1.underline
70 | # and rely on custom filtering to convert to LaTeX properly.
71 | styles = {
72 | 'font-weight:bold' => 'strong',
73 | 'font-weight:700' => 'strong',
74 | 'font-style:italic' => 'em',
75 | 'text-decoration:underline' => { class: 'underline' },
76 | }
77 |
78 | styles.each do |style, repl|
79 | @source.scan(/\.(c\d+)\{([^}]+;)*#{style}[;}]/).each do |cssClass,|
80 | @doc.css("span.#{cssClass}").each do |x|
81 | if Hash === repl
82 | x.replace("#{x.content}")
83 | else
84 | x.name = repl
85 | end
86 | end
87 | end
88 | end
89 | end
90 |
91 | # Replace first/last div with header/footer.
92 | def fixup_headers_footers
93 | @doc.css('div').each do |x|
94 | # header: first div in body
95 | if (!x.previous_sibling && !x.previous_element)
96 | x.replace("#{x.inner_text}
")
97 | next
98 | end
99 |
100 | # footer: last div in body
101 | if (!x.next_sibling && !x.next_element)
102 | x.replace("")
103 | end
104 | end
105 | end
106 |
107 | # Remove empty nodes: Google Docs has lots of them, especially with
108 | # pagebreaks.
109 | def fixup_empty_headers
110 | # must come before pagebreak processing
111 | doc.css('h1,h2,h3,h4,h5,h6').each do |x|
112 | x.remove if x.text =~ /^\s*$/
113 | end
114 | end
115 |
116 | # Rewrite page breaks into something pandoc can parse
117 | def fixup_page_breaks
118 | #
119 | doc.css('hr[style="page-break-before:always;display:none;"]').each do |x|
120 | x.replace("")
121 | end
122 | end
123 |
124 | # Get the zero-based depth of a list
125 | def list_depth(list)
126 | klasses = list['class'] or return 0
127 | klass = klasses.split.each do |klass|
128 | md = /^lst-kix_.*-(\d+)$/.match(klass) or next
129 | return md[1].to_i
130 | end
131 | return 0
132 | end
133 |
134 | # Google Docs exports nested lists as separate lists next to each other.
135 | def fixup_lists
136 | # Pass 1: Figure out the depth of each list
137 | depths = []
138 | @doc.css('ul, ol').each do |list|
139 | depth = list_depth(list)
140 | (depths[depth] ||= []) << list
141 | end
142 |
143 | # Pass 2: In reverse-depth order, coalesce lists
144 | depths.to_enum.with_index.reverse_each do |lists, depth|
145 | next unless lists
146 | lists.reverse_each do |list|
147 | # If the previous item is not a list, we're fine
148 | prev = list.previous_element
149 | next unless prev && prev.respond_to?(:name) &&
150 | %w[ol ul].include?(prev.name)
151 |
152 | if list_depth(prev) == depth
153 | # Same depth, append our li's to theirs
154 | prev.add_child(list.children)
155 | list.remove
156 | else
157 | # Lesser depth, append us to their last item
158 | prev.xpath('li').last.add_child(list)
159 | end
160 | end
161 | end
162 | end
163 |
164 | # Detect problems before we try to convert this doc
165 | def validate
166 | @errors = []
167 | validate_colspan
168 | unless @errors.empty?
169 | STDERR.puts 'Validation errors, bailing'
170 | @errors.each { |e| STDERR.puts e }
171 | exit 1
172 | end
173 | end
174 |
175 | # Detect colspan > 1
176 | def validate_colspan
177 | @doc.css('*[colspan]').
178 | select { |e| e.attr('colspan').to_i > 1 }.each do |e|
179 | found = true
180 | short = e.text[0, 30]
181 | @errors << "Colspan > 1 for \"#{short}\""
182 | end
183 | end
184 | end
185 |
--------------------------------------------------------------------------------
/lib/pandoc-filter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import json
4 | import sys
5 |
6 | """
7 | Pandoc filter to convert all level 2+ headers to paragraphs with
8 | emphasized text.
9 | """
10 |
11 | from pandocfilters import walk, RawBlock, RawInline, Span, attributes, Str
12 |
13 | def isH1WithClass(key, value, className):
14 | return key == 'Header' and value[0] == 1 and className in value[1][1]
15 |
16 | def isSubTitle(key, value):
17 | return isH1WithClass(key, value, u'ew-pandoc-subtitle')
18 |
19 | def isHeader(key, value):
20 | return isH1WithClass(key, value, u'ew-pandoc-header')
21 |
22 | def isFooter(key, value):
23 | return isH1WithClass(key, value, u'ew-pandoc-footer')
24 |
25 | # hacky workaround for pandoc's not supporting
26 | def isUnderline(key, value):
27 | # if key == 'Span' and value[0][1] == ['underline']:
28 | # sys.stderr.write( json.dumps(value[0]) + "\n")
29 | return key == 'Span' and value[0][1] == ['underline']
30 |
31 | # see https://github.com/jgm/pandoc/issues/1063
32 | def isTitle(key, value):
33 | return isH1WithClass(key, value, u'ew-pandoc-title')
34 |
35 | def isPageBreak(key, value):
36 | return isH1WithClass(key, value, u'ew-pandoc-pagebreak')
37 |
38 | def isHr(key, value):
39 | return key == 'HorizontalRule'
40 |
41 | # key is a pandoc type, generally "Header", "String" "Para",...
42 | # value is either a string (if key is String) or a list otherwise.
43 | # If a list, value's structure depends on type
44 | #
45 | # If key == 'Header', value will be like:
46 | #
47 | # {
48 | # "c":
49 | # [ 1,
50 | # ["", ["someClass", "someClass2"], []],
51 | # [{"c": "WordOne", "t": "Str"}, {"c": [], "t": "Space"}, {"c": "WordTwo", "t": "Str"}]
52 | # ],
53 | # "t": "Header"
54 | # }
55 | def extract_metadata(key, value, format, meta):
56 | # FIXME: isTitle fails!!!!
57 | if isTitle(key,value):
58 | meta["title"] = { "c": value[2], "t": "MetaInlines" }
59 | return []
60 | if isSubTitle(key,value):
61 | meta["subtitle"] = { "c": value[2], "t": "MetaInlines" }
62 | return []
63 | if isHeader(key,value):
64 | meta["header"] = { "c": value[2], "t": "MetaInlines" }
65 | return []
66 | if isFooter(key,value):
67 | meta["footer"] = { "c": value[2], "t": "MetaInlines" }
68 | return []
69 |
70 | def fix_hr(key, value, format, meta):
71 | if key == "HorizontalRule":
72 | return RawBlock('latex', '\\hrulefill')
73 |
74 | def fix_pagebreaks(key, value, format, meta):
75 | if isPageBreak(key,value):
76 | return RawBlock('latex', '\\pagebreak')
77 |
78 | def fix_underline(key, value, format, meta):
79 | if isUnderline(key,value):
80 | return [ RawInline('latex', '\\uline{'), Span(value[0], value[1]), RawInline('latex', '}') ]
81 |
82 | def toJSONFilter():
83 | doc = json.loads(sys.stdin.read())
84 | if len(sys.argv) > 1:
85 | format = sys.argv[1]
86 | else:
87 | format = ""
88 |
89 | newfmt = 'pandoc-api-version' in doc
90 |
91 | # first, process metadata (title and subtitle)
92 | if newfmt:
93 | result_meta = doc['meta']
94 | else:
95 | result_meta = doc[0]['unMeta']
96 | doc = walk(doc, extract_metadata, format, result_meta)
97 |
98 | # We need a title, use a default if unset
99 | if 'title' not in result_meta:
100 | title = {'c': 'Untitled', 't': 'Str'}
101 | result_meta['title'] = { "c": [title], "t": "MetaInlines" }
102 |
103 | if newfmt:
104 | doc['meta'] = result_meta
105 | else:
106 | doc[0]['unMeta'] = result_meta
107 |
108 | # then, fix page breaks
109 | doc = walk(doc, fix_pagebreaks, format, result_meta)
110 |
111 | # then, fix underline
112 | doc = walk(doc, fix_underline, format, result_meta)
113 |
114 | # then, customize horizontal rules (otherwise they're hardcoded in Writers/LaTeX.hs)
115 | doc = walk(doc, fix_hr, format, result_meta)
116 |
117 | json.dump(doc, sys.stdout)
118 |
119 | if __name__ == "__main__":
120 | toJSONFilter()
121 |
--------------------------------------------------------------------------------
/lib/pandoc-preprocess.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require_relative 'include/preprocess'
4 |
5 | html = ARGF.read
6 | preproc = PandocPreprocess.new(html)
7 | preproc.process
8 | preproc.download_resources
9 | puts preproc.doc.to_html(encoding: 'ASCII')
10 |
--------------------------------------------------------------------------------
/spec/empty_headers_spec.rb:
--------------------------------------------------------------------------------
1 | require 'preprocess'
2 |
3 | RSpec.describe PandocPreprocess, '#fixup_empty_headers' do
4 | it "removes a single empty h1" do
5 | preproc = PandocPreprocess.new('')
6 | preproc.fixup_empty_headers
7 | expect(preproc.doc.css('h1').size).to eq 0
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/spec/fixtures/sample-doc.html:
--------------------------------------------------------------------------------
1 | Test doc for gd-pandocSample Statement of Work
Every document needs a subtitle...
Prepared by:
Vendor Inc.,
49 Seven square
Montreal, QC H0H 0H0
phone: (514) 345-6789
fax: (514) 345-6789
http://vendor.com
Prepared for:
Client LLC,
37 Prime Lane
Montreal, QC H0H 0H0
phone: (514) 345-6789
fax: (514) 345-6789
http://supplier.com
CONFIDENTIAL: The contents of this document are confidential and are intended exclusively for the designated recipients. Other legal mumbo jumbo….
Agreement
This document is a contract between the writer and the reader.
Signed on behalf of Client LLC:
Name and title
Signature and date
Signed by Adam Smith, President of Vendor Inc.:
Signature and date
1. Background
Every project needs a background. Here’s some lorem ipsum about the project.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla sit amet arcu elit. Donec at massa et leo auctor fringilla vitae id erat. Nam feugiat velit vitae ornare tristique. Morbi porttitor, orci vel dapibus malesuada, magna libero ultrices massa, nec interdum diam nisi elementum ipsum. Vestibulum elit lorem, pretium id semper ac, aliquam ut dui. Etiam at magna non nibh viverra tristique. Maecenas consectetur vulputate tellus, ac rutrum felis molestie ut. Aenean tincidunt congue cursus. Maecenas in egestas magna, nec pellentesque justo. Phasellus ante quam, faucibus sit amet tellus et, fringilla convallis eros.
Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
Nunc at odio massa. In hac habitasse platea dictumst. Donec posuere erat ac ultrices suscipit. Quisque id velit nulla. Ut in urna orci. Sed at euismod neque. Sed velit velit, fringilla vitae eleifend eget, auctor id mi.
2. Proposed Solution
This wouldn’t be a proposal unless it proposed something. We will re-architect the system into a series of robust robust components:
- export, a script to export the data from access, validate it, refactor it into a logical format, then output it in plain-text format.
- import, a simple, robust module to handle file uploads of the cleaned data, and efficiently import this data. It will do sufficient error handling to allow full automation.
As we perform the refactoring, we will rewriting the code to ensure it’s documented, easy to test, and easy to update as future needs are identified.
Export component
- This will be a standalone script responsible for extracting data from source database, and cleaning it up as appropriate.
- The output format is human readable JSON files, with a logical, easy to understand structure
- Easy to verify output through manual inspection of output
- Include comprehensive automated tests to ensure output is correct
Import component
- Allows non-technical staff to upload already validated JSON files, and sync the target database accordingly.
- Assumes that input data is relatively safe, as it’s produced via the export component
- Will be optimized to perform quickly; typical runs will be completed within 60 seconds
- Improved validation and error handling
Benefits of our solution
Here’s some more lorem ipsum for you all. Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
Nunc at odio massa. In hac habitasse platea dictumst. Donec posuere erat ac ultrices suscipit. Quisque id velit nulla. Ut in urna orci. Sed at euismod neque. Sed velit velit, fringilla vitae eleifend eget, auctor id mi.
Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
Nullam vitae imperdiet enim. Ut id ultrices leo. In dictum bibendum vestibulum. Donec lobortis condimentum mauris ac interdum. Duis bibendum molestie eleifend. Nunc non neque a neque ornare euismod non ut sapien. Morbi sed diam quis felis commodo vehicula ac et augue. Maecenas pretium justo sit amet imperdiet pharetra.
3. Schedule and Budget
The work described in this mandate will take 300 hours to complete, to be performed within 6-8 weeks of the signing of this agreement.
4. Terms and Conditions
4.1 Staffing
Appropriate staff will be used for the execution of this project.
4.2 Relation to Other Agreements
This agreement is governed by the same terms and conditions as some other documents.
6. Language of Contract
Les parties ont convenu de rédiger cette entente en anglais. Parties have decided that this agreement be formulated in English.
--------------------------------------------------------------------------------
/spec/fixtures/simple-meta.html:
--------------------------------------------------------------------------------
1 | % Alex1
2 | % Alex2
3 | % Alex3
4 |
5 |
6 |
7 | TITLE
8 | SUBTITLE
9 |
10 |
11 | Sample heading
12 | Sample line
13 |
14 |
15 |
--------------------------------------------------------------------------------
/spec/fixtures/simple-pagebreak.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | FakeTitle FakeTitle
4 |
5 |
6 | Page One
7 | Stuff on page one.
8 |
9 | Page Two
10 | Stuff on page two.
11 |
12 |
13 |
--------------------------------------------------------------------------------
/spec/fixtures/simple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | FakeTitle FakeTitle
4 |
5 |
6 | AlexTitle AlexTitle
7 | AlexSubtitle AlexSubtitle
8 | Sample heading
9 | Sample line
10 |
11 |
12 |
--------------------------------------------------------------------------------
/spec/fixtures/simple.md:
--------------------------------------------------------------------------------
1 | % Alex
2 | % Alex2
3 | % Alex3
4 |
5 | # Heading l1
6 | ## Heading l2
7 | ### Heading l3
8 |
9 | text with *italics*
10 |
--------------------------------------------------------------------------------
/spec/headers_footers_spec.rb:
--------------------------------------------------------------------------------
1 | require 'preprocess'
2 |
3 | RSpec.describe PandocPreprocess, '#fixup_headers_footers' do
4 | it "turns the first div into a header" do
5 | preproc = PandocPreprocess.new('foo
')
6 | preproc.fixup_headers_footers
7 | expect(preproc.doc.css('h1').size).to eq 1
8 | expect(preproc.doc.at('h1')['class']).to eq 'ew-pandoc-header'
9 | expect(preproc.doc.css('div').size).to eq 0
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/image_parents_spec.rb:
--------------------------------------------------------------------------------
1 | require 'preprocess'
2 |
3 | RSpec.describe PandocPreprocess, '#fixup_image_parents' do
4 | it "unwraps an h1 containing an img" do
5 | preproc = PandocPreprocess.new('
')
6 | preproc.fixup_image_parents
7 | expect(preproc.doc.css('h1').size).to eq 0
8 | expect(preproc.doc.css('body > img').size).to eq 1
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/image_paths_spec.rb:
--------------------------------------------------------------------------------
1 | require 'preprocess'
2 |
3 | RSpec.describe PandocPreprocess, '#fixup_image_paths' do
4 | it "finds a single img" do
5 | preproc = PandocPreprocess.new('
')
6 | preproc.fixup_image_paths
7 | expect(preproc.downloads.size).to eq 1
8 | expect(preproc.downloads).to include('foo.jpg')
9 | expect(preproc.downloads['foo.jpg']).to eq 'foo'
10 | expect(preproc.doc.at('img')['src']).to eq 'foo.jpg'
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/spec/nested_lists_spec.rb:
--------------------------------------------------------------------------------
1 | require 'preprocess'
2 |
3 | RSpec.describe PandocPreprocess, '#fixup_lists' do
4 | it "does nothing if there are no lists" do
5 | preproc = PandocPreprocess.new('foo
')
6 | preproc.fixup_lists
7 | expect(preproc.doc.at('body').inner_html).to eq 'foo
'
8 | end
9 |
10 | it "coalesces consecutive lists" do
11 | preproc = PandocPreprocess.new(<
13 | foo
14 |
15 |
18 | EOF
19 | preproc.fixup_lists
20 | expect(preproc.doc.css('body > ul').size).to eq 1
21 | expect(preproc.doc.css('body > ul > li').size).to eq 2
22 | expect(preproc.doc.css('body > ul > li')[1].inner_text).to eq 'bar'
23 | end
24 |
25 | it "moves sublists into place" do
26 | preproc = PandocPreprocess.new(<
28 | foo
29 |
30 |
33 | EOF
34 | preproc.fixup_lists
35 | expect(preproc.doc.css('body > ul').size).to eq 1
36 | expect(preproc.doc.css('body > ul > li').size).to eq 1
37 | inner = preproc.doc.css('body > ul > li > ul')
38 | expect(inner.size).to eq 1
39 | expect(inner.first.inner_text.strip).to eq 'bar'
40 | end
41 |
42 | it "coalesces consecutive lists" do
43 | preproc = PandocPreprocess.new(<
45 | foo
46 |
47 |
50 |
53 | EOF
54 | preproc.fixup_lists
55 | doc = preproc.doc
56 | expect(doc.css('body > ul').size).to eq 1
57 | expect(doc.css('body > ul > li').size).to eq 2
58 | expect(doc.css('body > ul > li')[1].inner_text).to eq 'bar'
59 |
60 | inner = doc.css('body > ul > li > ul')
61 | expect(inner.size).to eq 1
62 | expect(inner.first.inner_text.strip).to eq 'iggy'
63 | end
64 |
65 |
66 | it "handles realistic input" do
67 | preproc = PandocPreprocess.new(<
69 | p1
70 |
71 |
72 |
73 | p2
74 |
75 |
76 |
77 | -
78 | foo
79 |
80 | -
81 | bar
82 |
83 |
84 |
89 |
90 | -
91 | baz
92 |
93 | -
94 | qux
95 |
96 |
97 |
98 | -
99 | blah
100 |
101 |
102 |
103 | -
104 | aaa
105 |
106 | -
107 | bbb
108 |
109 |
110 |
111 | -
112 | ccc
113 |
114 |
115 |
116 |
117 | p2
118 |
119 |
120 |
121 | -
122 | ccc
123 |
124 |
125 |
126 | -
127 | ddd
128 |
129 |
130 |
131 | -
132 | eee
133 |
134 |
135 |
136 |
137 |
138 |
139 | fdfafdaf
140 |
141 | EOF
142 |
143 | # Target result with all attributes stripped, whitespace changes ok
144 | target = <|^)?\s+(<|$)/, '\1\2')
145 | p1
146 | p2
147 |
148 | - foo
149 | - bar
150 | - iggy
151 | - baz
152 | - qux
153 |
154 | - blah
155 |
156 | - aaa
157 | - bbb
160 |
161 | p2
162 |
163 | - ccc
164 | - ddd
165 |
166 | - eee
167 |
168 | fdfafdaf
169 | EOF
170 |
171 | preproc.fixup_lists
172 | body = preproc.doc.at('body')
173 |
174 | # Remove attributes, strip space
175 | body.xpath('//*').each do |e|
176 | e.attributes.keys.each { |k| e.remove_attribute(k) }
177 | end
178 | result = body.inner_html.gsub(/(>|^)?\s+(<|$)/, '\1\2')
179 |
180 | expect(result).to eq target
181 | end
182 | end
183 |
184 |
--------------------------------------------------------------------------------
/spec/page_breaks_spec.rb:
--------------------------------------------------------------------------------
1 | require 'preprocess'
2 |
3 | RSpec.describe PandocPreprocess, '#fixup_page_breaks' do
4 | it "replaces a single page break with h1" do
5 | preproc = PandocPreprocess.new(
6 | '
')
7 | preproc.fixup_page_breaks
8 | expect(preproc.doc.css('hr').size).to eq 0
9 | expect(preproc.doc.css('h1').size).to eq 1
10 | expect(preproc.doc.at('h1')['class']).to eq 'ew-pandoc-pagebreak'
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/spec/span_styles_spec.rb:
--------------------------------------------------------------------------------
1 | require 'preprocess'
2 |
3 | RSpec.describe PandocPreprocess, '#fixup_span_styles' do
4 | it "replaces a bold class with a strong" do
5 | preproc = PandocPreprocess.new(<
7 |
10 |
11 |
12 | foo
13 |
14 | EOF
15 | preproc.fixup_span_styles
16 | expect(preproc.doc.css('span').size).to eq 0
17 | expect(preproc.doc.css('strong').size).to eq 1
18 | expect(preproc.doc.css('strong').text).to eq 'foo'
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib', 'include')
2 |
3 | # This file was generated by the `rspec --init` command. Conventionally, all
4 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
5 | # The generated `.rspec` file contains `--require spec_helper` which will cause
6 | # this file to always be loaded, without a need to explicitly require it in any
7 | # files.
8 | #
9 | # Given that it is always loaded, you are encouraged to keep this file as
10 | # light-weight as possible. Requiring heavyweight dependencies from this file
11 | # will add to the boot time of your test suite on EVERY test run, even for an
12 | # individual file that may not need all of that loaded. Instead, consider making
13 | # a separate helper file that requires the additional dependencies and performs
14 | # the additional setup, and require it from the spec files that actually need
15 | # it.
16 | #
17 | # The `.rspec` file also contains a few flags that are not defaults but that
18 | # users commonly want.
19 | #
20 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
21 | RSpec.configure do |config|
22 | # rspec-expectations config goes here. You can use an alternate
23 | # assertion/expectation library such as wrong or the stdlib/minitest
24 | # assertions if you prefer.
25 | config.expect_with :rspec do |expectations|
26 | # This option will default to `true` in RSpec 4. It makes the `description`
27 | # and `failure_message` of custom matchers include text for helper methods
28 | # defined using `chain`, e.g.:
29 | # be_bigger_than(2).and_smaller_than(4).description
30 | # # => "be bigger than 2 and smaller than 4"
31 | # ...rather than:
32 | # # => "be bigger than 2"
33 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true
34 | end
35 |
36 | # rspec-mocks config goes here. You can use an alternate test double
37 | # library (such as bogus or mocha) by changing the `mock_with` option here.
38 | config.mock_with :rspec do |mocks|
39 | # Prevents you from mocking or stubbing a method that does not exist on
40 | # a real object. This is generally recommended, and will default to
41 | # `true` in RSpec 4.
42 | mocks.verify_partial_doubles = true
43 | end
44 |
45 | # The settings below are suggested to provide a good initial experience
46 | # with RSpec, but feel free to customize to your heart's content.
47 | =begin
48 | # These two settings work together to allow you to limit a spec run
49 | # to individual examples or groups you care about by tagging them with
50 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples
51 | # get run.
52 | config.filter_run :focus
53 | config.run_all_when_everything_filtered = true
54 |
55 | # Allows RSpec to persist some state between runs in order to support
56 | # the `--only-failures` and `--next-failure` CLI options. We recommend
57 | # you configure your source control system to ignore this file.
58 | config.example_status_persistence_file_path = "spec/examples.txt"
59 |
60 | # Limits the available syntax to the non-monkey patched syntax that is
61 | # recommended. For more details, see:
62 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
63 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
64 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
65 | config.disable_monkey_patching!
66 |
67 | # This setting enables warnings. It's recommended, but in some cases may
68 | # be too noisy due to issues in dependencies.
69 | config.warnings = true
70 |
71 | # Many RSpec users commonly either run the entire suite or an individual
72 | # file, and it's useful to allow more verbose output when running an
73 | # individual spec file.
74 | if config.files_to_run.one?
75 | # Use the documentation formatter for detailed output,
76 | # unless a formatter has already been configured
77 | # (e.g. via a command-line flag).
78 | config.default_formatter = 'doc'
79 | end
80 |
81 | # Print the 10 slowest examples and example groups at the
82 | # end of the spec run, to help surface which specs are running
83 | # particularly slow.
84 | config.profile_examples = 10
85 |
86 | # Run specs in random order to surface order dependencies. If you find an
87 | # order dependency and want to debug it, you can fix the order by providing
88 | # the seed, which is printed after each run.
89 | # --seed 1234
90 | config.order = :random
91 |
92 | # Seed global randomization in this process using the `--seed` CLI option.
93 | # Setting this allows you to use `--seed` to deterministically reproduce
94 | # test failures related to randomization by passing the same `--seed` value
95 | # as the one that triggered the failure.
96 | Kernel.srand config.seed
97 | =end
98 | end
99 |
--------------------------------------------------------------------------------
/spec/titles_spec.rb:
--------------------------------------------------------------------------------
1 | require 'preprocess'
2 |
3 | RSpec.describe PandocPreprocess, '#fixup_titles' do
4 | it "annotates a single title" do
5 | preproc = PandocPreprocess.new('foo
')
6 | preproc.fixup_titles
7 |
8 | expect(preproc.doc.css('p').size).to eq 0
9 | expect(preproc.doc.css('h1').size).to eq 1
10 |
11 | h1 = preproc.doc.at('h1')
12 | expect(h1['class']).to eq 'ew-pandoc-title'
13 | expect(h1.text).to eq 'foo'
14 | end
15 | end
16 |
--------------------------------------------------------------------------------