Add tool scripts used to convert Markdown to HTML.

This commit is contained in:
Zhang Huangbin 2014-09-16 11:05:36 +08:00
commit 48d4cf2a6d
8 changed files with 526 additions and 0 deletions

1
3-faq-howto/_summary.md Normal file
View File

@ -0,0 +1 @@
This is most frequently asked questions.

1
3-faq-howto/_title.md Normal file
View File

@ -0,0 +1 @@
Frequently Asked Questions and Howto documents

View File

@ -0,0 +1,16 @@
# Amavisd + SpamAssassin not working, no mail header (X-Spam-*) inserted.
Amavisd has below setting in its config file by default:
File: /etc/amavisd/amavisd.conf
$sa_tag_level_deflt = 2.0;
That means Amavisd will insert `X-Spam-Flag` and other `X-Spam-*` headers when email score >= 2.0. If you want to let Amavisd always insert these headers, you can set it to a low score, for example:
$sa_tag_level_deflt = -999;
Amavisd's main config file is different on different Linux/BSD distributions:
* Red Hat, CentOS, OpenBSD: `/etc/amavisd/amavisd.conf`
* Debian, Ubuntu: `/etc/amavis/conf.d/50-user` (and other config files under `/etc/amavs/conf.d/`)
* FreeBSD: `/usr/local/etc/amavisd/amavisd.conf`

View File

@ -0,0 +1,61 @@
# How to enable SMTPS service (SMTP over SSL, port 465)
### Why iRedMail doesn't enable SMTPS (SMTP over SSL) by default
SMTPS is deprecated, so iRedMail disable it by default.
Quote from wikipedia.org: http://en.wikipedia.org/wiki/SMTPS
> Originally, in early 1997, the Internet Assigned Numbers Authority registered 465 for SMTPS. By the end of 1998, this was revoked when STARTTLS has been specified. With STARTTLS, the same port can be used with or without TLS. SMTP was seen as particularly important, because clients of this protocol are often other mail servers, which can not know whether a server they wish to communicate with will have a separate port for TLS. The port 465 is now registered for Source-Specific Multicast audio and video.
### Why enable SMTPS since it's depreciated
Unfortunately, there're some popular mail clients don't support submission (SMTP over STARTTLS, port 587), the famous one is Microsoft Outlook. Quote from wikipedia.org:
> Even in 2013, there are still services that continue to offer the deprecated SMTPS interface on port 465 in addition to (or instead of!) the RFC-compliant message submission interface on the port 587 defined by RFC 6409. Service providers that maintain port 465 do so because older Microsoft applications (including Entourage v10.0) do not support STARTTLS, and thus not the smtp-submission standard (ESMTPS on port 587). The only way for service providers to offer those clients an encrypted connection is to maintain port 465.
### How to enable SMTPS
To enable SMTPS, you should configure Postfix to listen on port 465 first, then open port 465 in iptables.
Please find below lines in Postfix config file `/etc/postfix/master.cf` (Linux/OpenBSD) or `/usr/local/etc/postfix/master.cf` (FreeBSD):
#smtps inet n - n - - smtpd
# -o smtpd_tls_wrappermode=yes
# -o smtpd_sasl_auth_enable=yes
# -o smtpd_client_restrictions=permit_sasl_authenticated,reject
# -o milter_macro_daemon_name=ORIGINATING
Uncomment first 4 lines, but leave the last one commented out (because iRedMail doesn't use Postfix milter at all):
smtps inet n - n - - smtpd
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
# -o milter_macro_daemon_name=ORIGINATING
Restart Postfix service to enable SMTPS.
### Open port 465 in iptables
On RHEL/CentOS, please update iptables rule file `/etc/sysconfig/iptables`, add one rule (third line in below code) for port 465, then restart iptables service.
# File: /etc/sysconfig/iptables
-A INPUT -p tcp --dport 25 -j ACCEPT
-A INPUT -p tcp --dport 587 -j ACCEPT
-A INPUT -p tcp --dport 465 -j ACCEPT
On Debian/Ubuntu, if you use iptables rule file provided by iRedMail, please update `/etc/default/iptables`, add one rule (third line in below code) for port 465, then restart iptables service.
File: /etc/sysconfig/iptables
-A INPUT -p tcp --dport 25 -j ACCEPT
-A INPUT -p tcp --dport 587 -j ACCEPT
-A INPUT -p tcp --dport 465 -j ACCEPT
On OpenBSD, please append service 'smtps' in `/etc/pf.conf`, parameter `mail_services=`:
File: /etc/pf.conf
mail_services="{www, https, submission, imap, imaps, pop3, pop3s, ssh, smtps}"
Reload PF rule file:
# pfctl -f /etc/pf.conf

16
README.md Normal file
View File

@ -0,0 +1,16 @@
如何用 Markdown 来写作电子书。主要仿 gitbooks.io
o 使用目录来区分各个章节。示例:
- 1-introduction/
|- _title.md
|- _description.md
|- 1-what_is_iredmail.md
|- 2-why_choose_iredmail.md
|- 3-price.md
- 2-faq/
- 3-install/
o 章节的介绍用目录里的 README.md 提供章节标题及章节的介绍性内容。
o 使用脚本 convert.sh 自动生成电子书的 index.html 文件,将章节按照顺序排列,生成完整的索引,并列出章节内部的所有文章。

95
convert.sh Normal file
View File

@ -0,0 +1,95 @@
#
# Syntax
#
# - Link: An [text](http://url.com/ "title")
#
# TODO
# - describe the directory structure and how it works
# - remove '1-', '2-' in html file name
# - incorrect CSS path in article html file
# - better way to strip prefix in dir/file name: [number]-
# Directory used to store converted html files.
PWD="$(PWD)"
SOURCE_DIR="${PWD}/src"
OUTPUT_DIR="${PWD}/output"
INDEX_MD="${OUTPUT_DIR}/index.md"
CMD_CONVERT="python ${PWD}/tools/markdown2html.py"
[ -d ${OUTPUT_DIR} ] || mkdir -p ${OUTPUT_DIR}
strip_name_prefix()
{
name="${1}"
name="$(echo ${name/#\.\//})" # ./filename
name="$(echo ${name/#[0-9][0-9][0-9]-/})" # nnn-
name="$(echo ${name/#[0-9][0-9]-/})" # nn-
name="$(echo ${name/#[0-9]-/})" # n-
echo "${name}"
}
# Get directories of chapters
all_chapter_dirs="$(find . -d 1 -type d -iname '[0-9]*' | sort)"
echo "* Found chapters:"
for dl in ${all_chapter_dirs}; do
echo " - $dl"
done
# Get chapter info
# - title: _title.md
# - summary: _summary.md
echo '' > ${INDEX_MD}
for chapter_dir in ${all_chapter_dirs}; do
# Get articles
all_chapter_articles="$(find ${chapter_dir} -depth 1 -type f -iname '[0-9a-z]*.md')"
echo "* ${chapter_dir} articles:"
for article in ${all_chapter_articles}; do
echo " - ${article}"
done
# Output directory.
# Remove prefix '[number]-' in chapter directory name.
_output_chapter_dir="${OUTPUT_DIR}/$(strip_name_prefix ${chapter_dir})"
_title_md="${chapter_dir}/_title.md"
_summary_md="${chapter_dir}/_summary.md"
if [ -f ${_title_md} ]; then
# generate index info of chapter
echo "# [$(cat ${_title_md})](${_output_chapter_dir}/_summary.html)" >> ${INDEX_MD}
fi
mkdir -p ${_output_chapter_dir} &>/dev/null
# Create ${_output_chapter_dir}/_summary.html
if [ -f ${_summary_md} ]; then
${CMD_CONVERT} ${_summary_md} ${_output_chapter_dir}
fi
# Article info:
# - title: first line (without '#') of markdown file
for article_file in ${all_chapter_articles}; do
article_file_basename="$(basename ${article_file})"
article_html_file="$(strip_name_prefix ${article_file_basename})"
# Replace '.md' suffix by '.html'
article_html_file="$(echo ${article_html_file/%.md/.html})"
# Get title.
_article_title="$(head -1 ${article_file} | awk -F'#' '{print $2}')"
#echo "article title: ${_article_title}"
echo "## [${_article_title}](${_output_chapter_dir}/${article_html_file})" >> ${INDEX_MD}
${CMD_CONVERT} ${article_file} ${_output_chapter_dir}
done
done
cd ${OUTPUT_DIR}
# Generate index.html
python ../tools/markdown2html.py ${INDEX_MD} .
# Cleanup
rm -f ${INDEX_MD}

263
css/markdown.css Normal file
View File

@ -0,0 +1,263 @@
body{
margin: 0 auto;
font-family: Georgia, Palatino, serif;
color: #444444;
line-height: 1;
max-width: 960px;
padding: 30px;
}
h1, h2, h3, h4 {
color: #111111;
font-weight: 400;
}
h1, h2, h3, h4, h5, p {
margin-bottom: 24px;
padding: 0;
}
h1 {
/*font-size: 48px;*/
font-size: 24px;
}
h2 {
/*font-size: 36px;*/
/* The bottom margin is small. It's designed to be used with gray meta text
* below a post title. */
/*margin: 24px 0 6px;*/
font-size: 16px;
margin: 10px 0 6px 20px;
}
h3 {
font-size: 24px;
}
h4 {
font-size: 21px;
}
h5 {
font-size: 18px;
}
a {
color: #0099ff;
margin: 0;
padding: 0;
vertical-align: baseline;
}
a:hover {
text-decoration: none;
color: #ff6600;
}
a:visited {
color: purple;
}
ul, ol {
padding: 0;
margin: 0;
}
li {
line-height: 24px;
}
li ul, li ul {
margin-left: 24px;
}
p, ul, ol {
font-size: 16px;
line-height: 24px;
max-width: 540px;
}
pre {
padding: 0px 24px;
max-width: 800px;
white-space: pre-wrap;
}
code {
font-family: Consolas, Monaco, Andale Mono, monospace;
line-height: 1.5;
font-size: 13px;
}
aside {
display: block;
float: right;
width: 390px;
}
blockquote {
border-left:.5em solid #eee;
padding: 0 2em;
margin-left:0;
max-width: 476px;
}
blockquote cite {
font-size:14px;
line-height:20px;
color:#bfbfbf;
}
blockquote cite:before {
content: '\2014 \00A0';
}
blockquote p {
color: #666;
max-width: 460px;
}
hr {
width: 540px;
text-align: left;
margin: 0 auto 0 0;
color: #999;
}
/* Code below this line is copyright Twitter Inc. */
button,
input,
select,
textarea {
font-size: 100%;
margin: 0;
vertical-align: baseline;
*vertical-align: middle;
}
button, input {
line-height: normal;
*overflow: visible;
}
button::-moz-focus-inner, input::-moz-focus-inner {
border: 0;
padding: 0;
}
button,
input[type="button"],
input[type="reset"],
input[type="submit"] {
cursor: pointer;
-webkit-appearance: button;
}
input[type=checkbox], input[type=radio] {
cursor: pointer;
}
/* override default chrome & firefox settings */
input:not([type="image"]), textarea {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
input[type="search"] {
-webkit-appearance: textfield;
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
label,
input,
select,
textarea {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 13px;
font-weight: normal;
line-height: normal;
margin-bottom: 18px;
}
input[type=checkbox], input[type=radio] {
cursor: pointer;
margin-bottom: 0;
}
input[type=text],
input[type=password],
textarea,
select {
display: inline-block;
width: 210px;
padding: 4px;
font-size: 13px;
font-weight: normal;
line-height: 18px;
height: 18px;
color: #808080;
border: 1px solid #ccc;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
select, input[type=file] {
height: 27px;
line-height: 27px;
}
textarea {
height: auto;
}
/* grey out placeholders */
:-moz-placeholder {
color: #bfbfbf;
}
::-webkit-input-placeholder {
color: #bfbfbf;
}
input[type=text],
input[type=password],
select,
textarea {
-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
-moz-transition: border linear 0.2s, box-shadow linear 0.2s;
transition: border linear 0.2s, box-shadow linear 0.2s;
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
-moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
}
input[type=text]:focus, input[type=password]:focus, textarea:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
-moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
}
/* buttons */
button {
display: inline-block;
padding: 4px 14px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 13px;
line-height: 18px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
-moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
background-color: #0064cd;
background-repeat: repeat-x;
background-image: -khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));
background-image: -moz-linear-gradient(top, #049cdb, #0064cd);
background-image: -ms-linear-gradient(top, #049cdb, #0064cd);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));
background-image: -webkit-linear-gradient(top, #049cdb, #0064cd);
background-image: -o-linear-gradient(top, #049cdb, #0064cd);
background-image: linear-gradient(top, #049cdb, #0064cd);
color: #fff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
border: 1px solid #004b9a;
border-bottom-color: #003f81;
-webkit-transition: 0.1s linear all;
-moz-transition: 0.1s linear all;
transition: 0.1s linear all;
border-color: #0064cd #0064cd #003f81;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
}
button:hover {
color: #fff;
background-position: 0 -15px;
text-decoration: none;
}
button:active {
-webkit-box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
-moz-box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
}
button::-moz-focus-inner {
padding: 0;
border: 0;
}

73
tools/markdown2html.py Normal file
View File

@ -0,0 +1,73 @@
"""Convert Markdown to HTML file.
Required Markdown module: http://pypi.python.org/pypi/Markdown/2.1.1
"""
# Usage:
# shell> python markdown2html.py path/to/file.md path/to/output/dir
import sys
import commands
import web
import markdown
# Markdown extensions
MD_EXTENSIONS = ['toc', 'meta', 'extra', 'footnotes', ]
# Get file name
filename = sys.argv[1]
# Get file name without file extension
filename_without_ext = filename.split('/')[-1].replace('.md', '')
# Get output directory
output_dir = sys.argv[2]
# Set output file name
output_html_file = output_dir + '/' + filename_without_ext + '.html'
# Get article title
title = commands.getoutput("""grep 'Title:' %s |awk -F'Title: ' '{print $2}'""" % filename)
# Set HTML head
html = """\
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>%(title)s</title>
<link href="../css/markdown.css" rel="stylesheet"></head>
</head>
<body>
<h3>%(title)s</h3>
""" % {'title': title}
# Read markdown file and render as HTML body
# Handle unicode characters with web.safeunicode
orig_content = web.safeunicode(open(filename).read())
html += markdown.markdown(orig_content, extensions=MD_EXTENSIONS)
# HTML foot
'''
html += """\
<!-- Google Analytics -->
<!--
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("UA-3293801-14");
pageTracker._trackPageview();
} catch(err) {}
</script>
-->
<!-- End Google Analytics -->
"""
'''
html += '</body></html>'
# Write to file
f = open(output_html_file, 'w')
f.write(html)
f.close()