go开源Hugo站点构建三步曲之集结渲染

目录
  • Assemble
    • PageState
    • 动手实践 - Show Me the Code of Create a PageState
  • Render
    • 动手实践 - Show Me the Code of Publish

Assemble

Assemble所做的事情很纯粹,那就是创建站点页面实例 - pageState。 因为支持多站点,contentMaps有多个。 所以Assemble不仅要创建pageState,还需要管理好所有的pages,这就用到了PageMaps。

type pageMap struct {
	s *Site
	*contentMap
}
type pageMaps struct {
	workers *para.Workers
	pmaps   []*pageMap
}

实际上pageMap就是由contentMap组合而来的。 而contentMap中的组成树的结点就是contentNode。

正好,每个contentNode又对应一个pageState。

type contentNode struct {
	p *pageState
	// Set if source is a file.
	// We will soon get other sources.
	fi hugofs.FileMetaInfo
	// The source path. Unix slashes. No leading slash.
	path string
	...
}

所以Assemble不仅要为前面Process处理过生成的contentNode创建pageState,还要补齐一些缺失的contentNode,如Section。

PageState

可以看出,Assemble的重点就是组建PageState,那她到底长啥样:

type pageState struct {
	// This slice will be of same length as the number of global slice of output
	// formats (for all sites).
	pageOutputs []*pageOutput
	// This will be shifted out when we start to render a new output format.
	*pageOutput
	// Common for all output formats.
	*pageCommon
	...
}

从注解中可以看出普通信息将由pageCommon提供,而输出信息则由pageOutput提供。 比较特殊的是pageOutputs,是pageOutput的数组。 在 基础架构中,对这一点有作分析。 这要归因于Hugo的多站点渲染策略 - 允许在不同的站点中重用其它站点的页面。

// hugo-playground/hugolib/page__new.go
// line 97
// Prepare output formats for all sites.
// We do this even if this page does not get rendered on
// its own. It may be referenced via .Site.GetPage and
// it will then need an output format.
ps.pageOutputs = make([]*pageOutput, len(ps.s.h.renderFormats))

那在Assemble中Hugo是如何组织pageState实例的呢?

从上图中,可以看出Assemble阶段主要是新建pageState。 其中pageOutput在这一阶段只是一个占位符,空的nopPageOutput。 pageCommon则是在这一阶段给赋予了很多的信息,像meta相关的信息,及各种细节信息的providers。

动手实践 - Show Me the Code of Create a PageState

package main
import (
	"fmt"
	"html/template"
)
func main() {
	outputFormats := createOutputFormats()
	renderFormats := initRenderFormats(outputFormats)
	s := &site{
		outputFormats: outputFormats,
		renderFormats: renderFormats,
	}
	ps := &pageState{
		pageOutputs: nil,
		pageOutput:  nil,
		pageCommon:  &pageCommon{m: &pageMeta{kind: KindPage}},
	}
	ps.init(s)
	// prepare
	ps.pageOutput = ps.pageOutputs[0]
	// render
	fmt.Println(ps.targetPaths().TargetFilename)
	fmt.Println(ps.Content())
	fmt.Println(ps.m.kind)
}
type site struct {
	outputFormats map[string]Formats
	renderFormats Formats
}
type pageState struct {
	// This slice will be of same length as the number of global slice of output
	// formats (for all sites).
	pageOutputs []*pageOutput
	// This will be shifted out when we start to render a new output format.
	*pageOutput
	// Common for all output formats.
	*pageCommon
}
func (p *pageState) init(s *site) {
	pp := newPagePaths(s)
	p.pageOutputs = make([]*pageOutput, len(s.renderFormats))
	for i, f := range s.renderFormats {
		ft, found := pp.targetPaths[f.Name]
		if !found {
			panic("target path not found")
		}
		providers := struct{ targetPather }{ft}
		po := &pageOutput{
			f:                      f,
			pagePerOutputProviders: providers,
			ContentProvider:        nil,
		}
		contentProvider := newPageContentOutput(po)
		po.ContentProvider = contentProvider
		p.pageOutputs[i] = po
	}
}
func newPageContentOutput(po *pageOutput) *pageContentOutput {
	cp := &pageContentOutput{
		f: po.f,
	}
	initContent := func() {
		cp.content = template.HTML("<p>hello content</p>")
	}
	cp.initMain = func() {
		initContent()
	}
	return cp
}
func newPagePaths(s *site) pagePaths {
	outputFormats := s.renderFormats
	targets := make(map[string]targetPathsHolder)
	for _, f := range outputFormats {
		target := "/" + "blog" + "/" + f.BaseName +
			"." + f.MediaType.SubType
		paths := TargetPaths{
			TargetFilename: target,
		}
		targets[f.Name] = targetPathsHolder{
			paths: paths,
		}
	}
	return pagePaths{
		targetPaths: targets,
	}
}
type pagePaths struct {
	targetPaths map[string]targetPathsHolder
}
type targetPathsHolder struct {
	paths TargetPaths
}
func (t targetPathsHolder) targetPaths() TargetPaths {
	return t.paths
}
type pageOutput struct {
	f Format
	// These interface provides the functionality that is specific for this
	// output format.
	pagePerOutputProviders
	ContentProvider
	// May be nil.
	cp *pageContentOutput
}
// pageContentOutput represents the Page content for a given output format.
type pageContentOutput struct {
	f        Format
	initMain func()
	content  template.HTML
}
func (p *pageContentOutput) Content() any {
	p.initMain()
	return p.content
}
// these will be shifted out when rendering a given output format.
type pagePerOutputProviders interface {
	targetPather
}
type targetPather interface {
	targetPaths() TargetPaths
}
type TargetPaths struct {
	// Where to store the file on disk relative to the publish dir. OS slashes.
	TargetFilename string
}
type ContentProvider interface {
	Content() any
}
type pageCommon struct {
	m *pageMeta
}
type pageMeta struct {
	// kind is the discriminator that identifies the different page types
	// in the different page collections. This can, as an example, be used
	// to to filter regular pages, find sections etc.
	// Kind will, for the pages available to the templates, be one of:
	// page, home, section, taxonomy and term.
	// It is of string type to make it easy to reason about in
	// the templates.
	kind string
}
func initRenderFormats(
	outputFormats map[string]Formats) Formats {
	return outputFormats[KindPage]
}
func createOutputFormats() map[string]Formats {
	m := map[string]Formats{
		KindPage: {HTMLFormat},
	}
	return m
}
const (
	KindPage = "page"
)
var HTMLType = newMediaType("text", "html")
// HTMLFormat An ordered list of built-in output formats.
var HTMLFormat = Format{
	Name:      "HTML",
	MediaType: HTMLType,
	BaseName:  "index",
}
func newMediaType(main, sub string) Type {
	t := Type{
		MainType:  main,
		SubType:   sub,
		Delimiter: "."}
	return t
}
type Type struct {
	MainType  string `json:"mainType"`  // i.e. text
	SubType   string `json:"subType"`   // i.e. html
	Delimiter string `json:"delimiter"` // e.g. "."
}
type Format struct {
	// The Name is used as an identifier. Internal output formats (i.e. HTML and RSS)
	// can be overridden by providing a new definition for those types.
	Name string `json:"name"`
	MediaType Type `json:"-"`
	// The base output file name used when not using "ugly URLs", defaults to "index".
	BaseName string `json:"baseName"`
}
type Formats []Format

输出结果:

/blog/index.html
<p>hello content</p>
page
Program exited.

PageState线上可直接运行版本

Render

基础信息是由pageCommon提供了,那渲染过程中的输出由谁提供呢?

没错,轮到pageOutput了:

可以看到,在render阶段,pageState的pageOutput得到了最终的处理,为发布做准备了。 为了发布,最重的信息是发布什么,以及发布到哪里去。 这些信息都在pageOutput中,其中ContentProvider是提供发布内容的,而targetPathsProvider则是提供发布地址信息的。 其中地址信息主要来源于PagePath,这又和站点的RenderFormats和OutputFormats相关,哪下图所示:

其中OutputFormats, RenderFormats及PageOutput之间的关系有在 基础架构中有详细提到,这里就不再赘述。

// We create a pageOutput for every output format combination, even if this
// particular page isn't configured to be rendered to that format.
type pageOutput struct {
	...
	// These interface provides the functionality that is specific for this
	// output format.
	pagePerOutputProviders
	page.ContentProvider
	page.TableOfContentsProvider
	page.PageRenderProvider
	// May be nil.
	cp *pageContentOutput
}

其中pageContentOutput正是实现了ContentProvider接口的实例。 其中有包含markdown文件原始信息的workContent字段,以及包含处理过后的内容content字段。 如Hugo Shortcode特性。 就是在这里经过contentToRender方法将原始信息进行处理,而最终实现的。

动手实践 - Show Me the Code of Publish

package main
import (
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"
)
// publisher needs to know:
// 1: what to publish
// 2: where to publish
func main() {
	// 1
	// src is template executed result
	// it is the source that we need to publish
	// take a look at template executor example
	// https://c.sunwei.xyz/template-executor.html
	src := &bytes.Buffer{}
	src.Write([]byte("template executed result"))
	b := &bytes.Buffer{}
	transformers := createTransformerChain()
	if err := transformers.Apply(b, src); err != nil {
		fmt.Println(err)
		return
	}
	dir, _ := os.MkdirTemp("", "hugo")
	defer os.RemoveAll(dir)
	// 2
	// targetPath is from pageState
	// this is where we need to publish
	// take a look at page state example
	// https://c.sunwei.xyz/page-state.html
	targetPath := filepath.Join(dir, "index.html")
	if err := os.WriteFile(
		targetPath,
		bytes.TrimSuffix(b.Bytes(), []byte("\n")),
		os.ModePerm); err != nil {
		panic(err)
	}
	fmt.Println("1. what to publish: ", string(b.Bytes()))
	fmt.Println("2. where to publish: ", dir)
}
func (c *Chain) Apply(to io.Writer, from io.Reader) error {
	fb := &bytes.Buffer{}
	if _, err := fb.ReadFrom(from); err != nil {
		return err
	}
	tb := &bytes.Buffer{}
	ftb := &fromToBuffer{from: fb, to: tb}
	for i, tr := range *c {
		if i > 0 {
			panic("switch from/to and reset to")
		}
		if err := tr(ftb); err != nil {
			continue
		}
	}
	_, err := ftb.to.WriteTo(to)
	return err
}
func createTransformerChain() Chain {
	transformers := NewEmpty()
	transformers = append(transformers, func(ft FromTo) error {
		content := ft.From().Bytes()
		w := ft.To()
		tc := bytes.Replace(
			content,
			[]byte("result"), []byte("transferred result"), 1)
		_, _ = w.Write(tc)
		return nil
	})
	return transformers
}
// Chain is an ordered processing chain. The next transform operation will
// receive the output from the previous.
type Chain []Transformer
// Transformer is the func that needs to be implemented by a transformation step.
type Transformer func(ft FromTo) error
// FromTo is sent to each transformation step in the chain.
type FromTo interface {
	From() BytesReader
	To() io.Writer
}
// BytesReader wraps the Bytes method, usually implemented by bytes.Buffer, and an
// io.Reader.
type BytesReader interface {
	// Bytes The slice given by Bytes is valid for use only until the next buffer modification.
	// That is, if you want to use this value outside of the current transformer step,
	// you need to take a copy.
	Bytes() []byte
	io.Reader
}
// NewEmpty creates a new slice of transformers with a capacity of 20.
func NewEmpty() Chain {
	return make(Chain, 0, 2)
}
// Implements contentTransformer
// Content is read from the from-buffer and rewritten to to the to-buffer.
type fromToBuffer struct {
	from *bytes.Buffer
	to   *bytes.Buffer
}
func (ft fromToBuffer) From() BytesReader {
	return ft.from
}
func (ft fromToBuffer) To() io.Writer {
	return ft.to
}

输出结果:

1. what to publish:  template executed transferred result
2. where to publish:  /tmp/hugo2834984546
Program exited.

Publish线上可直接运行版本

以上就是go开源Hugo站点构建三步曲之集结渲染的详细内容,更多关于go Hugo站点构建集结渲染的资料请关注我们其它相关文章!

(0)

相关推荐

  • Hugo Config模块构建实现源码剖析

    目录 了然于胸 - collectModules时序图 抽象总结 - 输入不同类型的值,输出标准的configProvider 动手实践 - Show Me the Code of collectModules 了然于胸 - collectModules时序图 经过loadConfig和applyConfigDefaults,我们已经将用户自定义信息和默认信息都归置妥当,并且放在了Config Provider中,方便查用. Hugo在拿到这些信息后,立马着手的事情就是collectModule

  • go开源Hugo站点渲染之模板词法解析

    目录 正文 了然于胸 - newTemplateExec时序图 词法解析 - parse 正文 Deps在准备好NewPathSpec,NewSpec,NewContentSpec,NewSourceSpec后,调用onCreate正式创建HugoSites,并在最后一步,加载模板执行器. 模板执行器只是提前将模板信息转换成了模板执行器,如何使用并没有涉及到. 为了让我们对模板生命周期有更全面的了解,让我们以robots.txt为例,来看看是如何通过模板执行器生成最终文件的. 了然于胸 - ne

  • go开源Hugo站点构建三步曲之集结渲染

    目录 Assemble PageState 动手实践 - Show Me the Code of Create a PageState Render 动手实践 - Show Me the Code of Publish Assemble Assemble所做的事情很纯粹,那就是创建站点页面实例 - pageState. 因为支持多站点,contentMaps有多个. 所以Assemble不仅要创建pageState,还需要管理好所有的pages,这就用到了PageMaps. type pageM

  • Mybatis实现自定义的typehandler三步曲

    第一步实现接口TypeHandler<T> @MappedJdbcTypes(JdbcType.TIMESTAMP)//此处如果不使用该注解,在myabtis-config.xml中注册该typehandler的时候需要写明jdbctype="TIMESTAMP" @MappedTypes(DateTime.class)//此处如果不使用该注解,在myabtis-config.xml中注册该typehandler的时候需要写明javatype="com.sinos

  • ubuntu 16.04 64位兼容32位程序三步曲

    第一步:确认自己系统的架构 dpkg --print-architecture 输出: amd64 结果为 amd64 表示系统是64位的 第二步:确认打开了多架构支持功能 dpkg --print-foreign-architectures 输出: i386 如果这里没有输出i386,则需要打开多架构支持 sudo dpkg --add-architecture i386 sudo apt-get update 第三步:安装对应的32位的库 sudo apt-get dist-upgrade

  • Go1.18新特性之泛型使用三步曲(小结)

    目录 01 Go中的泛型是什么 1.1 传统的函数编写方式 1.2 泛型函数编写方式 02 从泛型被加入之前说起 2.1 针对每一种类型编写一套重复的代码 2.2 使用空接口并通过类型断言来判定具体的类型 2.3 传递空接口并使用反射解析具体类型 2.4 通过自定义接口类型实现 03 深入理解泛型--泛型使用“三步曲” 3.1 第一步:类型参数化 3.2 第二步:给类型添加约束 3.3 第三步:类型参数实例化 04 泛型类型约束和普通接口的区别 总结 01 Go中的泛型是什么 众所周知,Go是一

  • 打造计数器DIY三步曲(中)

    本篇将介绍在自己的镜像站上也能放上自己的计数器.     我以假设您有多个镜像站为例,首先在数据表中插入几个新的数据num.visited,这在你的镜像站上将用的着,只要将上篇中的count1.php改一下即可.     先建立一个文件夹count,里面放上您要用的文件count2.php,count3.php--,举个例子,如count2的源码,将count1.php改成: <? $linkptr=mysql_pconnect("localhost","yournam

  • 打造计数器DIY三步曲(下)

    中篇我向您介绍了怎样在自己的镜像站上放置OSO上的计数器,本篇我将向您介绍如何打造自己的免费计数器系统的构思,程序还得您自己写哟.     好多网站都提供了免费计数器服务,很久以前我就有个梦想,建立自己的免费计数器系统,而现在OSO提供了数据库,这一切将成为现实.     多数网站都是只向会员提供免费计数器的,我们也可以这样做,好让自己的网站也有上一些臣民,在他们注册的时候,可以自动生成一个ID,我们的计数器系统就全靠这个ID了!     当用户申请计数器时,首先当然是检查用户名和密码了,登陆成

  • 打造计数器DIY三步曲(上)

    人大多都很向往使用数据库来做自己的计数器,留言本,论坛之类的东西了,本篇将介绍本人自制的计数器程序.     首先建立0-9这10个GIF数字图片,放在img文件夹下,再建立一个PHP文件 --count1.php,程序如下: <? $linkptr=mysql_pconnect("localhost","yourname","password"); mysql_select_db("yourname",$linkptr

  • 简单三步,搞掂内存泄漏

    原文地址:http://www.jackslocum.com/blog/2006/10/02/3-easy-steps-to-avoid-javascript-memory-leaks/ 你可能还未知道,你浏览的大多数的js网站,会引起 内存泄漏.听起来有点夸张,但这是事实,难道我会骗你吗?泄漏监视器 Leak Monitor 这是个方便的FireFox扩展,当你离开那页的时候它便会指向JavsScript对象,如果出现泄漏的话会弹出一个窗口显示细节内容,而且能够告诉你是那个对象或函数引起的泄漏

  • Windows下mongodb安装与配置三步走

    前言 最近在重新学习node,所以和同事一起搞了个模仿新浪微博的项目,项目刚开始,所以其他的东西就暂时先不提.这里介绍下mongodb的安装.直接搜索可以看到很多介绍,但是我第一次是失败了,不过看了好几个还是搞成了,接下来我通过三个步骤来介绍下(我这里windows环境哦). 一.简介 MongoDB一种非关系型数据库(NoSql),是一种强大.灵活.可扩展的数据存储方式,因为MongoDB是文档模型,自由灵活很高,可以让你在开发过程中畅顺无比,对于大数据量.高并发.弱事务的互联网应用,Mong

  • 简单三步轻松实现ORACLE字段自增

    第一步:创建一个表. 复制代码 代码如下: create table Test_Table ( ID number(11) primary key, Name varchar(50) not null, Value1 varchar(50) not null ) 第二步:创建一个自增序列以此提供调用函数. 复制代码 代码如下: create sequence AutoID start with 1 //根据需要自己可修改该数值 increment by 1 //步长值 minvalue 1 no

随机推荐