markdown中寫下你的文章,并使用Python將它們轉(zhuǎn)換成HTML-作者Florian Dahlitz,于2020年5月18日(15分鐘)
介紹
幾個(gè)月前,我想開通自己的博客,而不是使用像Medium這樣的網(wǎng)站。這是一個(gè)非常基礎(chǔ)的博客,所有的文章都是HTML形式的。然而,有一天,我突然產(chǎn)生了自己編寫Markdown到HTML生成器的想法,最終這將允許我用markdown來編寫文章。此外,為它添加諸如估計(jì)閱讀時(shí)間之類的擴(kuò)展特性會(huì)更容易。長(zhǎng)話短說,我實(shí)現(xiàn)了自己的markdown到HTML生成器,我真的很喜歡它!
在本系列文章中,我想向您展示如何構(gòu)建自己的markdown到HTML生成器。該系列由三部分組成:
第一部分(本文)介紹了整個(gè)管線的實(shí)現(xiàn)。
第二部分通過一個(gè)模塊擴(kuò)展了實(shí)現(xiàn)的管線,該模塊用于計(jì)算給定文章的預(yù)計(jì)閱讀時(shí)間。
第三部分演示如何使用管線生成自己的RSS摘要。
這三部分中使用的代碼都可以在GitHub上找到。
備注:我的文章中markdown到HTML生成器的想法基于Anthony Shaw文章中的實(shí)現(xiàn)。
項(xiàng)目構(gòu)建
為了遵循本文的內(nèi)容,您需要安裝幾個(gè)軟件包。我們把它們放進(jìn)requirements.txt文件。
Markdown是一個(gè)包,它允許您將markdown代碼轉(zhuǎn)換為HTML。之后我們用Flask產(chǎn)生靜態(tài)文件。
但在安裝之前,請(qǐng)創(chuàng)建一個(gè)虛擬環(huán)境,以避免Python安裝出現(xiàn)問題:
激活后,您可以使用pip安裝requirements.txt中的依賴。
很好!讓我們創(chuàng)建幾個(gè)目錄來更好地組織代碼。首先,我們創(chuàng)建一個(gè)app目錄。此目錄包含我們提供博客服務(wù)的Flask應(yīng)用程序。所有后續(xù)目錄都將在app目錄內(nèi)創(chuàng)建。其次,我們創(chuàng)建一個(gè)名為posts的目錄。此目錄包含要轉(zhuǎn)換為HTML文件的markdown文件。接下來,我們創(chuàng)建一個(gè)templates目錄,其中包含稍后使用Flask展示的模板。在templates目錄中,我們?cè)賱?chuàng)建兩個(gè)目錄:
posts包含生成的HTML文件,這些文件與應(yīng)用程序根目錄中posts目錄中的文件相對(duì)應(yīng)。
shared包含在多個(gè)文件中使用的HTML文件。
此外,我們還創(chuàng)建了一個(gè)名為services的目錄。該目錄將包含我們?cè)贔lask應(yīng)用程序中使用的模塊,或者為它生成某些東西。最后,創(chuàng)建一個(gè)名為static的目錄帶有兩個(gè)子目錄images和css。自定義CSS文件和文章的縮略圖將存儲(chǔ)在此處。
您的最終項(xiàng)目結(jié)構(gòu)應(yīng)如下所示:
令人驚嘆!我們完成了一般的項(xiàng)目設(shè)置。我們來看看Flask的設(shè)置。
Flask設(shè)置
路由
我們?cè)谏弦还?jié)安裝了Flask。但是,我們?nèi)匀恍枰粋€(gè)Python文件來定義用戶可以訪問的端點(diǎn)。在app目錄中創(chuàng)建main.py并將以下內(nèi)容復(fù)制到其中。
該文件定義了一個(gè)具有兩個(gè)端點(diǎn)的基礎(chǔ)版Flask應(yīng)用程序。用戶可以使用/route訪問第一個(gè)端點(diǎn)返回索引頁,其中列出了所有文章。
第二個(gè)端點(diǎn)是更通用的端點(diǎn)。它接受post的名稱并返回相應(yīng)的HTML文件。
接下來,我們通過向app目錄中添加一個(gè)__init__.py,將其轉(zhuǎn)換為一個(gè)Python包。此文件為空。如果您使用UNIX計(jì)算機(jī),則可以從項(xiàng)目的根目錄運(yùn)行以下命令:
模板
現(xiàn)在,我們創(chuàng)建兩個(gè)模板文件index.html以及l(fā)ayout.html,都存儲(chǔ)在templates/shared目錄中。這個(gè)layout.html模板將用于單個(gè)博客條目,而index.html模板用于生成索引頁,從中我們可以訪問每個(gè)帖子。讓我們從index.html模板開始。
它是一個(gè)基本的HTML文件,其中有兩個(gè)元標(biāo)記、一個(gè)標(biāo)題和兩個(gè)樣式表。注意,我們使用一個(gè)遠(yuǎn)程樣式表和一個(gè)本地樣式表。遠(yuǎn)程樣式表用于啟用Bootstrap[1]類。第二個(gè)是自定義樣式。我們晚點(diǎn)再定義它們。
HTML文件的主體包含一個(gè)容器,其中包含Jinja2[2]邏輯,用于為每個(gè)post生成Bootstrap卡片[3]。您是否注意到我們不直接基于變量名訪問這些值,而是需要將[0]添加到其中?這是因?yàn)槲恼轮薪馕龅脑獢?shù)據(jù)是列表。實(shí)際上,每個(gè)元數(shù)據(jù)元素都是由單一元素組成的列表。我們稍后再看。到目前為止,還不錯(cuò)。讓我們看看layout.html模板。
如你所見,它比前一個(gè)短一點(diǎn),簡(jiǎn)單一點(diǎn)。文件頭與index.html文件很相似,除了我們有不同的標(biāo)題。當(dāng)然,我們可以共用一個(gè)模板,但是我不想讓事情變得更復(fù)雜。
body中的容器僅定義一個(gè)h1標(biāo)記。然后,我們提供給模板的內(nèi)容被插入并呈現(xiàn)。
樣式
正如上一節(jié)所承諾的,我們將查看自定義CSS文件style.css. 我們?cè)趕tatic/css中找到該文件,并根據(jù)需要自定義頁面。下面是我們將用于基礎(chǔ)示例的內(nèi)容:
我不喜歡Bootstrap中blockquotes的默認(rèn)外觀,所以我們?cè)谧髠?cè)添加了一點(diǎn)間距和邊框。此外,blockquote段落底部的頁邊空白將被刪除。不刪除的話看起來很不自然。
最后但并非最不重要的是,左右兩邊的填充被刪除。由于兩邊都有額外的填充,縮略圖沒有正確對(duì)齊,所以在這里刪除它們。
到現(xiàn)在為止,一直都還不錯(cuò)。我們完成了關(guān)于Flask的所有工作。讓我們開始寫一些帖子吧!
寫文章
正如標(biāo)題所承諾的,你可以用markdown寫文章-是的!在寫文章的時(shí)候,除了保證正確的markdown格式外,沒有其他需要注意的事情。
在完成本文之后,我們需要在文章中添加一些元數(shù)據(jù)。此元數(shù)據(jù)添加在文章之前,并由三個(gè)破折號(hào)分隔開來---。下面是一個(gè)示例文章(post1.md)的摘錄:
注意:您可以在GitHub庫的app/posts/post1.md中找到完整的示例文章。
在我們的例子中,元數(shù)據(jù)由標(biāo)題、副標(biāo)題、類別、發(fā)布日期和index.html中卡片對(duì)應(yīng)縮略圖的路徑組成.
我們?cè)贖TML文件中使用了元數(shù)據(jù),你還記得嗎?元數(shù)據(jù)規(guī)范必須是有效的YAML。示例形式是鍵后面跟著一個(gè)冒號(hào)和值。最后,冒號(hào)后面的值是列表中的第一個(gè)也是唯一的元素。這就是我們通過模板中的索引運(yùn)算符訪問這些值的原因。
假設(shè)我們寫完了文章。在我們可以開始轉(zhuǎn)換之前,還有一件事要做:我們需要為我們的帖子生成縮略圖!為了讓事情更簡(jiǎn)單,只需從你的電腦或網(wǎng)絡(luò)上隨機(jī)選取一張圖片,命名它為placeholder.jpg并把它放到static/images目錄中。GitHub存儲(chǔ)庫中兩篇文章的元數(shù)據(jù)包含一個(gè)代表圖像的鍵值對(duì),值是placeholder.jpg。
注意:在GitHub存儲(chǔ)庫中,您可以找到我提到的兩篇示例文章。
markdown到HTML轉(zhuǎn)換器
最后,我們可以開始實(shí)現(xiàn)markdown to HTML轉(zhuǎn)換器。因此,我們使用我們?cè)陂_始時(shí)安裝的第三方包Markdown。我們先創(chuàng)建一個(gè)新模塊,轉(zhuǎn)換服務(wù)將在其中運(yùn)行。因此,我們?cè)趕ervice目錄中創(chuàng)建了converter.py。我們一步一步看完整個(gè)腳本。您可以在GitHub存儲(chǔ)庫中一次查看整個(gè)腳本。
首先,我們導(dǎo)入所需的所有內(nèi)容并創(chuàng)建幾個(gè)常量:
ROOT指向我們項(xiàng)目的根。因此,它是包含app的目錄。
POSTS_DIR是以markdown編寫的文章的路徑。
TEMPLATE_DIR分別指向?qū)?yīng)的templates目錄。
BLOG_TEMPLATE_文件存儲(chǔ)layout.html的路徑。
INDEX_TEMPLATE_FILE是index.html
BASE_URL是我們項(xiàng)目的默認(rèn)地址,例如。https://florian-dahlitz.de.默認(rèn)值(如果不是通過環(huán)境變量DOMAIN提供的話)是http://0.0.0.0:5000。
接下來,我們創(chuàng)建一個(gè)名為generate_entries的新函數(shù)。這是我們定義的唯一一個(gè)轉(zhuǎn)換文章的函數(shù)。
在函數(shù)中,我們首先獲取POSTS_DIR目錄中所有markdown文件的路徑。pathlib的awesome glob函數(shù)幫助我們實(shí)現(xiàn)它。
此外,我們定義了Markdown包需要使用的擴(kuò)展。默認(rèn)情況下,本文中使用的所有擴(kuò)展都隨它的安裝一起提供。
注意:您可以在文檔[4]中找到有關(guān)擴(kuò)展的更多信息。
此外,我們實(shí)例化了一個(gè)新的文件加載程序,并創(chuàng)建了一個(gè)在轉(zhuǎn)換項(xiàng)目時(shí)使用的環(huán)境。隨后,將創(chuàng)建一個(gè)名為all_posts的空列表。此列表將包含我們處理后的所有帖子。現(xiàn)在,我們進(jìn)入for循環(huán)并遍歷POSTS_DIR中找到的所有文章。
我們啟動(dòng)for循環(huán),并打印當(dāng)前正在處理的post的路徑。如果有什么東西出問題了,這尤其有用。然后我們就知道,哪個(gè)文章的轉(zhuǎn)換失敗了。
接下來,我們?cè)谀J(rèn)url之后增加一部分。假設(shè)我們有一篇標(biāo)題為“面向初學(xué)者的Python”的文章。我們將文章存儲(chǔ)在一個(gè)名為python-for-beginners.md,的文件中,因此生成的url將是http://0.0.0.0:5000/posts/python-for-beginners。
變量url_html存儲(chǔ)的字符串與url相同,只是我們?cè)谀┪蔡砑恿?html。我們使用此變量定義另一個(gè)稱為target_file.的變量。變量指向存儲(chǔ)相應(yīng)HTML文件的位置。
最后,我們定義了一個(gè)變量md,它表示markdown.Markdown的實(shí)例,用于將markdown代碼轉(zhuǎn)換為HTML。您可能會(huì)問自己,為什么我們沒有在for循環(huán)之前實(shí)例化這個(gè)實(shí)例,而是在內(nèi)部實(shí)例化。當(dāng)然,對(duì)于我們這里的小例子來說,這沒有什么區(qū)別(只是執(zhí)行時(shí)間稍微短一點(diǎn))。但是,如果使用諸如腳注之類的擴(kuò)展來使用腳注,則需要為每個(gè)帖子實(shí)例化一個(gè)新實(shí)例,因?yàn)槟_注添加后就不會(huì)從此實(shí)例中刪除。因此,如果您的第一篇文章使用了一些腳注,那么即使您沒有明確定義它們,所有其他文章也將具有相同的腳注。
讓我們轉(zhuǎn)到for循環(huán)中的第一個(gè)with代碼塊。
實(shí)際上,with代碼塊打開當(dāng)前post并將其內(nèi)容讀入變量content。之后調(diào)用_md.convert將以markdown方式寫入的內(nèi)容轉(zhuǎn)換為HTML。隨后,env環(huán)境根據(jù)提供的模板BLOG_TEMPLATE_FILE(即layout.html如果你還記得的話)渲染生成的HTML。
第二個(gè)with 代碼塊用于將第一個(gè)with 代碼塊中創(chuàng)建的文檔寫入目標(biāo)文件。
以下三行代碼從元數(shù)據(jù)中獲取發(fā)布日期(被發(fā)布的日期),將其轉(zhuǎn)換為正確的格式(RFC 2822),并將其分配回文章的元數(shù)據(jù)。此外,生成的post_dict被添加到all_posts列表中。
我們現(xiàn)在出了for循環(huán),因此,我們遍歷了posts目錄中找到的所有posts并對(duì)其進(jìn)行了處理。讓我們看看generate_entries函數(shù)中剩下的三行代碼。
我們按日期倒序?qū)ξ恼逻M(jìn)行排序,所以首先顯示最新的文章。隨后,我們將文章寫到模板目錄一個(gè)新創(chuàng)建的index.html文件中。別把index.html錯(cuò)認(rèn)為templates/shared目錄中的那個(gè)。templates/shared目錄中的是模板,這個(gè)是我們要使用Flask服務(wù)的生成的。
最后我們?cè)诤瘮?shù)generate_entries之后添加以下if語句。
這意味著如果我們通過命令行執(zhí)行文件,它將調(diào)用generate_entries函數(shù)。
太棒了,我們完成了converter.py腳本!讓我們從項(xiàng)目的根目錄運(yùn)行以下命令來嘗試:
您應(yīng)該看到一些正在轉(zhuǎn)換的文件的路徑。假設(shè)您編寫了兩篇文章或使用了GitHub存儲(chǔ)庫中的兩篇文章,那么您應(yīng)該在templates目錄中找到三個(gè)新創(chuàng)建的文件。首先是index.html,它直接位于templates目錄中,其次是templates/posts目錄中的兩個(gè)HTML文件,它們對(duì)應(yīng)于markdown文件。
最后啟動(dòng)Flask應(yīng)用程序并轉(zhuǎn)到http://0.0.0.0:5000。
總結(jié)
太棒了,你完成了這個(gè)系列的第一部分!在本文中,您已經(jīng)學(xué)習(xí)了如何利用Markdown包創(chuàng)建自己的Markdown to HTML生成器。您實(shí)現(xiàn)了整個(gè)管線,它是高度可擴(kuò)展的,您將在接下來的文章中看到這一點(diǎn)。
希望你喜歡這篇文章。一定要和你的朋友和同事分享。如果你還沒有,考慮在Twitter上關(guān)注我@DahlitzF或者訂閱我的通知,這樣你就不會(huì)錯(cuò)過任何即將發(fā)表的文章。保持好奇心,不斷編碼!
參考文獻(xiàn)
Bootstrap (http://getbootstrap.com/)
Primer on Jinja Templating (https://realpython.com/primer-on-jinja-templating/)
Bootstrap Card (https://getbootstrap.com/docs/4.4/components/card/)
Python-Markdown Extensions (https://python-markdown.github.io/extensions/)
Tweet
英文原文:https://florian-dahlitz.de/blog/build-a-markdown-to-html-conversion-pipeline-using-python
譯者:阿布銩
Vue的方法中,可以使用JavaScript的循環(huán)語句來實(shí)現(xiàn)循環(huán)功能。常用的循環(huán)語句有for循環(huán)、while循環(huán)和do-while循環(huán)。
1. for循環(huán):
for循環(huán)適用于已知循環(huán)次數(shù)的情況,通過指定初始值、循環(huán)條件和每次循環(huán)后的操作來控制循環(huán)次數(shù)。
```javascript
for (let i = 0; i < 10; i++) {
// 循環(huán)體
console.log(i);
}
```
2. while循環(huán):
while循環(huán)適用于未知循環(huán)次數(shù)的情況,通過指定循環(huán)條件來控制循環(huán)是否繼續(xù)。
```javascript
let i = 0;
while (i < 10) {
// 循環(huán)體
console.log(i);
i++;
}
```
3. do-while循環(huán):
do-while循環(huán)與while循環(huán)類似,不同之處在于循環(huán)體至少會(huì)執(zhí)行一次,然后再根據(jù)循環(huán)條件決定是否繼續(xù)循環(huán)。
```javascript
let i = 0;
do {
//循環(huán)體
console.log(i);
i++;
} while (i < 10);
```
在Vue中,可以將循環(huán)語句放在方法中,然后在模板中通過調(diào)用該方法來實(shí)現(xiàn)循環(huán)功能。例如,可以使用v-for指令來循環(huán)渲染一個(gè)數(shù)組中的元素。
```html
<template>
<div>
<ul>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{ id: 1, name: 'item1' },
{ id: 2, name: 'item2' },
{ id: 3, name: 'item3' }
]
};
}
};
</script>
```
上述代碼中,通過v-for指令循環(huán)渲染了一個(gè)數(shù)組中的元素,每個(gè)元素都被渲染為一個(gè)li標(biāo)簽。通過:key指定每個(gè)元素的唯一標(biāo)識(shí),可以提高渲染性能。
前端開發(fā)來說,通過動(dòng)畫來提升交互效果是很常見的。在很早以前,做web動(dòng)畫主要通過javascript或者jquery或者flash這樣的手段,非常麻煩,自打有了ccs3,做動(dòng)畫就太方便了,只需幾行css代碼就可以搞定。
這里我們就演示一個(gè)常見的循環(huán)滾動(dòng)效果,任務(wù)是這樣:先準(zhǔn)備一個(gè)圖片,平鋪到頁面上充滿整個(gè)屏幕,然后就讓畫面一直向上循環(huán)滾動(dòng),形成無邊無際的感覺。
雖然可以從網(wǎng)上搜到一些類似的代碼,但是魚龍混雜,無關(guān)緊要的代碼非常多,不夠純粹。如果要弄明白動(dòng)畫的原理,只有自己動(dòng)手做一遍才能真正消化吃透。所以我們來一步步原創(chuàng)這個(gè)代碼,排除所有不必要的基礎(chǔ)樣式,只說要點(diǎn),3個(gè)步驟你就可以完全掌握其精髓!
第一步:布局
首先,滾動(dòng)的圖片需要放在一個(gè)容器里,一行html代碼即可完成:
第二步:把圖片放進(jìn)容器
css中body的邊界設(shè)為0,把容器設(shè)高度100%以充滿屏幕,再調(diào)用背景圖pic.png
第三部:讓畫面動(dòng)起來
咱不做標(biāo)題黨,循環(huán)滾動(dòng)靠的就是3行css動(dòng)起來的。
先是1行 -webkit-animation屬性:4個(gè)參數(shù)分別表示:動(dòng)畫名稱scroll,1秒時(shí)長(zhǎng),移動(dòng)速度為線性的,無限循環(huán)。
然后是對(duì)應(yīng)的關(guān)鍵幀 @-webkit-keyframes 屬性,這是自己定義的動(dòng)畫規(guī)則,只需寫2行規(guī)則即可:
原理:動(dòng)畫就是畫從一個(gè)地方動(dòng)到另一個(gè)地方。對(duì)普通滾動(dòng)效果來說,有起點(diǎn)和終點(diǎn)這兩個(gè)節(jié)點(diǎn)的位置就夠了。所以我們用0%和100%分別表示起點(diǎn)和終點(diǎn),指定2個(gè)背景圖的xy位置坐標(biāo)即可。圖片會(huì)在規(guī)定時(shí)間內(nèi)從起點(diǎn)移動(dòng)到終點(diǎn),并循環(huán)下去,數(shù)值是負(fù)表示是向上移動(dòng)。320px正好是圖片的高度,這樣循環(huán)的時(shí)候是無縫銜接的。
好了,最終完整的代碼如下,是不是很精練呢?保存成 index.html 即可
代碼寫完了,還要記得在當(dāng)前目錄要有pic.png這個(gè)圖片哦,我隨便畫了幾筆,絕無觀賞性,建議自己找個(gè)好看點(diǎn)的圖片來代替。
現(xiàn)在用瀏覽器打開index.html即可看到效果,比較魔性的地方在于,如果你盯著看久了,關(guān)閉窗口以后會(huì)出現(xiàn)幻覺,仿佛整個(gè)顯示器都在向上飛,哈哈!
最后我們來說說瀏覽器兼容性問題:
大家可能注意到了,前面那2個(gè)古怪的 -webkit-animation, @-webkit-keyframes 這里的-webkit-其實(shí)是一個(gè)前綴,animation和@keyframes才是CSS的標(biāo)準(zhǔn)屬性。
當(dāng)加上-webkit-后,就形成了一個(gè)針對(duì)特殊瀏覽器的專有屬性,表示用在谷歌的chrome和蘋果的safari瀏覽器上。此外還有-moz前綴代表針對(duì)firefox瀏覽器的私有屬性。
所以我們?cè)谟玫絚ss3的一些特性的時(shí)候,經(jīng)常使用一大堆的重復(fù)性的代碼,比如我們今天的這個(gè)代碼,有人會(huì)寫成這個(gè)樣子:
一個(gè)簡(jiǎn)單的動(dòng)畫就要寫這么多冗余的代碼,為的只是支持一些舊的瀏覽器,有必要嗎?為什么在這個(gè)例子中我們僅僅采用了-webkit-而沒有使用其它專有屬性呢?
因?yàn)楝F(xiàn)在已經(jīng)是2019年了!谷歌蘋果的瀏覽器是主流,占據(jù)了絕大部分,而其它小眾瀏覽器也大多能夠兼容他們,在版本上,大部分人安裝瀏覽器是直接下載新版本安裝使用,而非找出家里陳年的老軟盤、老光盤去安裝,家中的老電腦也早已升級(jí)不知多少回了,所以也幾乎沒有機(jī)會(huì)使用低版本的瀏覽器了!
至于微軟的IE,就更別提了,IE9以前不支持動(dòng)畫的,只能用js或者jquery來寫動(dòng)畫,直到IE10才支持css動(dòng)畫,隨后IE被放棄,主推Edge,搞了幾天越來越頭大干脆也放棄,現(xiàn)在直接使用chrome內(nèi)核了,所以針對(duì)ie的兼容性除非有特殊要求已經(jīng)無需考慮。
你在網(wǎng)上能看到的范例代碼,如果有寫成那么復(fù)雜臃腫的,估計(jì)也都是3-5年前發(fā)的老文,或者抄來抄去不做思考的搬磚工留下的“初學(xué)者”筆記。
我們不仿測(cè)試一下幾款主流瀏覽器的情況看看,結(jié)論:
測(cè)試結(jié)果表明,-webkit-的寫法在4款不同內(nèi)核的瀏覽器上都能正常使用,所以我們的代碼因此能得以簡(jiǎn)化。
當(dāng)然,這個(gè)例子也有局限性,比如你看,只有蘋果safari不支持標(biāo)準(zhǔn)寫法,萬一將來他改邪歸正了呢?畢竟標(biāo)準(zhǔn)寫法才是眾望所歸不是?使用針對(duì)個(gè)別瀏覽器的私有屬性寫法,雖可用但畢竟有些怪怪的,將來怎么樣還很難說呢。這樣看來,如果使用古老的處理辦法,重復(fù)N次為每個(gè)專屬瀏覽器各寫一份代碼,除了辣眼睛也真沒什么錯(cuò)。
瀏覽器的兼容問題涉及面實(shí)在是非常廣,三言兩語還真說不完,以后會(huì)專門來講。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。