2019-01-02 06:33:26 -05:00
// Copyright 2019 The Hugo Authors. All rights reserved.
2017-05-25 05:32:02 -04:00
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hugolib
import (
"fmt"
"math/rand"
"path"
"path/filepath"
"testing"
"time"
2019-08-10 15:05:17 -04:00
qt "github.com/frankban/quicktest"
2019-01-02 06:33:26 -05:00
"github.com/gohugoio/hugo/resources/page"
2017-06-13 12:42:45 -04:00
"github.com/gohugoio/hugo/deps"
2017-05-25 05:32:02 -04:00
)
const pageCollectionsPageTemplate = ` -- -
title : "%s"
categories :
- Hugo
-- -
# Doc
`
func BenchmarkGetPage ( b * testing . B ) {
var (
cfg , fs = newTestCfg ( )
r = rand . New ( rand . NewSource ( time . Now ( ) . UnixNano ( ) ) )
)
2023-01-04 12:24:36 -05:00
configs , err := loadTestConfigFromProvider ( cfg )
if err != nil {
b . Fatal ( err )
}
2017-05-25 05:32:02 -04:00
for i := 0 ; i < 10 ; i ++ {
for j := 0 ; j < 100 ; j ++ {
writeSource ( b , fs , filepath . Join ( "content" , fmt . Sprintf ( "sect%d" , i ) , fmt . Sprintf ( "page%d.md" , j ) ) , "CONTENT" )
}
}
2023-01-04 12:24:36 -05:00
s := buildSingleSite ( b , deps . DepsCfg { Fs : fs , Configs : configs } , BuildCfg { SkipRender : true } )
2017-05-25 05:32:02 -04:00
pagePaths := make ( [ ] string , b . N )
for i := 0 ; i < b . N ; i ++ {
pagePaths [ i ] = fmt . Sprintf ( "sect%d" , r . Intn ( 10 ) )
}
b . ResetTimer ( )
for i := 0 ; i < b . N ; i ++ {
2018-07-17 05:18:29 -04:00
home , _ := s . getPageNew ( nil , "/" )
2017-05-25 05:32:02 -04:00
if home == nil {
b . Fatal ( "Home is nil" )
}
2018-07-17 05:18:29 -04:00
p , _ := s . getPageNew ( nil , pagePaths [ i ] )
2017-05-25 05:32:02 -04:00
if p == nil {
b . Fatal ( "Section is nil" )
}
}
}
2019-09-10 05:26:34 -04:00
func createGetPageRegularBenchmarkSite ( t testing . TB ) * Site {
2017-05-25 05:32:02 -04:00
var (
2019-09-10 05:26:34 -04:00
c = qt . New ( t )
2017-05-25 05:32:02 -04:00
cfg , fs = newTestCfg ( )
)
2023-01-04 12:24:36 -05:00
configs , err := loadTestConfigFromProvider ( cfg )
if err != nil {
t . Fatal ( err )
}
2019-09-10 05:26:34 -04:00
pc := func ( title string ) string {
return fmt . Sprintf ( pageCollectionsPageTemplate , title )
}
2017-05-25 05:32:02 -04:00
for i := 0 ; i < 10 ; i ++ {
for j := 0 ; j < 100 ; j ++ {
2019-09-10 05:26:34 -04:00
content := pc ( fmt . Sprintf ( "Title%d_%d" , i , j ) )
writeSource ( c , fs , filepath . Join ( "content" , fmt . Sprintf ( "sect%d" , i ) , fmt . Sprintf ( "page%d.md" , j ) ) , content )
2017-05-25 05:32:02 -04:00
}
}
2023-01-04 12:24:36 -05:00
return buildSingleSite ( c , deps . DepsCfg { Fs : fs , Configs : configs } , BuildCfg { SkipRender : true } )
2019-09-10 05:26:34 -04:00
}
2017-05-25 05:32:02 -04:00
2019-09-10 05:26:34 -04:00
func TestBenchmarkGetPageRegular ( t * testing . T ) {
c := qt . New ( t )
s := createGetPageRegularBenchmarkSite ( t )
2017-05-25 05:32:02 -04:00
2019-09-10 05:26:34 -04:00
for i := 0 ; i < 10 ; i ++ {
pp := path . Join ( "/" , fmt . Sprintf ( "sect%d" , i ) , fmt . Sprintf ( "page%d.md" , i ) )
page , _ := s . getPageNew ( nil , pp )
c . Assert ( page , qt . Not ( qt . IsNil ) , qt . Commentf ( pp ) )
2017-05-25 05:32:02 -04:00
}
}
2019-09-10 05:26:34 -04:00
func BenchmarkGetPageRegular ( b * testing . B ) {
r := rand . New ( rand . NewSource ( time . Now ( ) . UnixNano ( ) ) )
b . Run ( "From root" , func ( b * testing . B ) {
s := createGetPageRegularBenchmarkSite ( b )
c := qt . New ( b )
pagePaths := make ( [ ] string , b . N )
for i := 0 ; i < b . N ; i ++ {
pagePaths [ i ] = path . Join ( fmt . Sprintf ( "/sect%d" , r . Intn ( 10 ) ) , fmt . Sprintf ( "page%d.md" , r . Intn ( 100 ) ) )
}
b . ResetTimer ( )
for i := 0 ; i < b . N ; i ++ {
page , _ := s . getPageNew ( nil , pagePaths [ i ] )
c . Assert ( page , qt . Not ( qt . IsNil ) )
}
} )
b . Run ( "Page relative" , func ( b * testing . B ) {
s := createGetPageRegularBenchmarkSite ( b )
c := qt . New ( b )
allPages := s . RegularPages ( )
pagePaths := make ( [ ] string , b . N )
pages := make ( [ ] page . Page , b . N )
for i := 0 ; i < b . N ; i ++ {
pagePaths [ i ] = fmt . Sprintf ( "page%d.md" , r . Intn ( 100 ) )
pages [ i ] = allPages [ r . Intn ( len ( allPages ) / 3 ) ]
}
b . ResetTimer ( )
for i := 0 ; i < b . N ; i ++ {
page , _ := s . getPageNew ( pages [ i ] , pagePaths [ i ] )
c . Assert ( page , qt . Not ( qt . IsNil ) )
}
} )
}
type getPageTest struct {
name string
2018-07-19 10:46:36 -04:00
kind string
2019-01-02 06:33:26 -05:00
context page . Page
2019-09-10 05:26:34 -04:00
pathVariants [ ] string
2018-07-19 10:46:36 -04:00
expectedTitle string
}
2019-09-10 05:26:34 -04:00
func ( t * getPageTest ) check ( p page . Page , err error , errorMsg string , c * qt . C ) {
c . Helper ( )
2019-08-10 15:05:17 -04:00
errorComment := qt . Commentf ( errorMsg )
2018-07-19 10:46:36 -04:00
switch t . kind {
case "Ambiguous" :
2019-08-10 15:05:17 -04:00
c . Assert ( err , qt . Not ( qt . IsNil ) )
c . Assert ( p , qt . IsNil , errorComment )
2018-07-19 10:46:36 -04:00
case "NoPage" :
2019-08-10 15:05:17 -04:00
c . Assert ( err , qt . IsNil )
c . Assert ( p , qt . IsNil , errorComment )
2018-07-19 10:46:36 -04:00
default :
2019-08-10 15:05:17 -04:00
c . Assert ( err , qt . IsNil , errorComment )
c . Assert ( p , qt . Not ( qt . IsNil ) , errorComment )
c . Assert ( p . Kind ( ) , qt . Equals , t . kind , errorComment )
c . Assert ( p . Title ( ) , qt . Equals , t . expectedTitle , errorComment )
2018-07-19 10:46:36 -04:00
}
}
2017-05-25 05:32:02 -04:00
func TestGetPage ( t * testing . T ) {
var (
cfg , fs = newTestCfg ( )
2019-08-10 15:05:17 -04:00
c = qt . New ( t )
2017-05-25 05:32:02 -04:00
)
2023-01-04 12:24:36 -05:00
configs , err := loadTestConfigFromProvider ( cfg )
c . Assert ( err , qt . IsNil )
2019-09-10 05:26:34 -04:00
pc := func ( title string ) string {
return fmt . Sprintf ( pageCollectionsPageTemplate , title )
}
2017-05-25 05:32:02 -04:00
for i := 0 ; i < 10 ; i ++ {
for j := 0 ; j < 10 ; j ++ {
2019-09-10 05:26:34 -04:00
content := pc ( fmt . Sprintf ( "Title%d_%d" , i , j ) )
2017-05-25 05:32:02 -04:00
writeSource ( t , fs , filepath . Join ( "content" , fmt . Sprintf ( "sect%d" , i ) , fmt . Sprintf ( "page%d.md" , j ) ) , content )
}
}
2019-09-10 05:26:34 -04:00
content := pc ( "home page" )
2018-07-19 10:46:36 -04:00
writeSource ( t , fs , filepath . Join ( "content" , "_index.md" ) , content )
2019-09-10 05:26:34 -04:00
content = pc ( "about page" )
2018-07-19 10:46:36 -04:00
writeSource ( t , fs , filepath . Join ( "content" , "about.md" ) , content )
2019-09-10 05:26:34 -04:00
content = pc ( "section 3" )
2018-07-19 10:46:36 -04:00
writeSource ( t , fs , filepath . Join ( "content" , "sect3" , "_index.md" ) , content )
2019-09-10 05:26:34 -04:00
writeSource ( t , fs , filepath . Join ( "content" , "sect3" , "unique.md" ) , pc ( "UniqueBase" ) )
writeSource ( t , fs , filepath . Join ( "content" , "sect3" , "Unique2.md" ) , pc ( "UniqueBase2" ) )
2017-05-26 03:12:19 -04:00
2019-09-10 05:26:34 -04:00
content = pc ( "another sect7" )
2018-07-19 10:46:36 -04:00
writeSource ( t , fs , filepath . Join ( "content" , "sect3" , "sect7" , "_index.md" ) , content )
2019-09-10 05:26:34 -04:00
content = pc ( "deep page" )
2018-07-19 10:46:36 -04:00
writeSource ( t , fs , filepath . Join ( "content" , "sect3" , "subsect" , "deep.md" ) , content )
2019-09-10 05:26:34 -04:00
// Bundle variants
writeSource ( t , fs , filepath . Join ( "content" , "sect3" , "b1" , "index.md" ) , pc ( "b1 bundle" ) )
writeSource ( t , fs , filepath . Join ( "content" , "sect3" , "index" , "index.md" ) , pc ( "index bundle" ) )
2020-05-21 05:25:00 -04:00
writeSource ( t , fs , filepath . Join ( "content" , "section_bundle_overlap" , "_index.md" ) , pc ( "index overlap section" ) )
writeSource ( t , fs , filepath . Join ( "content" , "section_bundle_overlap_bundle" , "index.md" ) , pc ( "index overlap bundle" ) )
2023-01-04 12:24:36 -05:00
s := buildSingleSite ( t , deps . DepsCfg { Fs : fs , Configs : configs } , BuildCfg { SkipRender : true } )
2017-05-25 05:32:02 -04:00
2018-07-19 10:46:36 -04:00
sec3 , err := s . getPageNew ( nil , "/sect3" )
2019-08-10 15:05:17 -04:00
c . Assert ( err , qt . IsNil )
c . Assert ( sec3 , qt . Not ( qt . IsNil ) )
2018-07-19 10:46:36 -04:00
2019-09-10 05:26:34 -04:00
tests := [ ] getPageTest {
2018-07-19 10:46:36 -04:00
// legacy content root relative paths
2019-09-10 05:26:34 -04:00
{ "Root relative, no slash, home" , page . KindHome , nil , [ ] string { "" } , "home page" } ,
{ "Root relative, no slash, root page" , page . KindPage , nil , [ ] string { "about.md" , "ABOUT.md" } , "about page" } ,
{ "Root relative, no slash, section" , page . KindSection , nil , [ ] string { "sect3" } , "section 3" } ,
{ "Root relative, no slash, section page" , page . KindPage , nil , [ ] string { "sect3/page1.md" } , "Title3_1" } ,
{ "Root relative, no slash, sub setion" , page . KindSection , nil , [ ] string { "sect3/sect7" } , "another sect7" } ,
{ "Root relative, no slash, nested page" , page . KindPage , nil , [ ] string { "sect3/subsect/deep.md" } , "deep page" } ,
{ "Root relative, no slash, OS slashes" , page . KindPage , nil , [ ] string { filepath . FromSlash ( "sect5/page3.md" ) } , "Title5_3" } ,
{ "Short ref, unique" , page . KindPage , nil , [ ] string { "unique.md" , "unique" } , "UniqueBase" } ,
{ "Short ref, unique, upper case" , page . KindPage , nil , [ ] string { "Unique2.md" , "unique2.md" , "unique2" } , "UniqueBase2" } ,
{ "Short ref, ambiguous" , "Ambiguous" , nil , [ ] string { "page1.md" } , "" } ,
2018-07-19 10:46:36 -04:00
// ISSUE: This is an ambiguous ref, but because we have to support the legacy
// content root relative paths without a leading slash, the lookup
// returns /sect7. This undermines ambiguity detection, but we have no choice.
//{"Ambiguous", nil, []string{"sect7"}, ""},
2019-09-10 05:26:34 -04:00
{ "Section, ambigous" , page . KindSection , nil , [ ] string { "sect7" } , "Sect7s" } ,
{ "Absolute, home" , page . KindHome , nil , [ ] string { "/" , "" } , "home page" } ,
{ "Absolute, page" , page . KindPage , nil , [ ] string { "/about.md" , "/about" } , "about page" } ,
{ "Absolute, sect" , page . KindSection , nil , [ ] string { "/sect3" } , "section 3" } ,
{ "Absolute, page in subsection" , page . KindPage , nil , [ ] string { "/sect3/page1.md" , "/Sect3/Page1.md" } , "Title3_1" } ,
{ "Absolute, section, subsection with same name" , page . KindSection , nil , [ ] string { "/sect3/sect7" } , "another sect7" } ,
{ "Absolute, page, deep" , page . KindPage , nil , [ ] string { "/sect3/subsect/deep.md" } , "deep page" } ,
2020-12-02 07:23:25 -05:00
{ "Absolute, page, OS slashes" , page . KindPage , nil , [ ] string { filepath . FromSlash ( "/sect5/page3.md" ) } , "Title5_3" } , // test OS-specific path
2019-09-10 05:26:34 -04:00
{ "Absolute, unique" , page . KindPage , nil , [ ] string { "/sect3/unique.md" } , "UniqueBase" } ,
{ "Absolute, unique, case" , page . KindPage , nil , [ ] string { "/sect3/Unique2.md" , "/sect3/unique2.md" , "/sect3/unique2" , "/sect3/Unique2" } , "UniqueBase2" } ,
2020-12-02 07:23:25 -05:00
// next test depends on this page existing
2018-07-19 10:46:36 -04:00
// {"NoPage", nil, []string{"/unique.md"}, ""}, // ISSUE #4969: this is resolving to /sect3/unique.md
2019-09-10 05:26:34 -04:00
{ "Absolute, missing page" , "NoPage" , nil , [ ] string { "/missing-page.md" } , "" } ,
{ "Absolute, missing section" , "NoPage" , nil , [ ] string { "/missing-section" } , "" } ,
2018-07-19 10:46:36 -04:00
// relative paths
2019-09-10 05:26:34 -04:00
{ "Dot relative, home" , page . KindHome , sec3 , [ ] string { ".." } , "home page" } ,
{ "Dot relative, home, slash" , page . KindHome , sec3 , [ ] string { "../" } , "home page" } ,
{ "Dot relative about" , page . KindPage , sec3 , [ ] string { "../about.md" } , "about page" } ,
{ "Dot" , page . KindSection , sec3 , [ ] string { "." } , "section 3" } ,
{ "Dot slash" , page . KindSection , sec3 , [ ] string { "./" } , "section 3" } ,
{ "Page relative, no dot" , page . KindPage , sec3 , [ ] string { "page1.md" } , "Title3_1" } ,
{ "Page relative, dot" , page . KindPage , sec3 , [ ] string { "./page1.md" } , "Title3_1" } ,
{ "Up and down another section" , page . KindPage , sec3 , [ ] string { "../sect4/page2.md" } , "Title4_2" } ,
{ "Rel sect7" , page . KindSection , sec3 , [ ] string { "sect7" } , "another sect7" } ,
{ "Rel sect7 dot" , page . KindSection , sec3 , [ ] string { "./sect7" } , "another sect7" } ,
{ "Dot deep" , page . KindPage , sec3 , [ ] string { "./subsect/deep.md" } , "deep page" } ,
{ "Dot dot inner" , page . KindPage , sec3 , [ ] string { "./subsect/../../sect7/page9.md" } , "Title7_9" } ,
2020-12-02 07:23:25 -05:00
{ "Dot OS slash" , page . KindPage , sec3 , [ ] string { filepath . FromSlash ( "../sect5/page3.md" ) } , "Title5_3" } , // test OS-specific path
2019-09-10 05:26:34 -04:00
{ "Dot unique" , page . KindPage , sec3 , [ ] string { "./unique.md" } , "UniqueBase" } ,
{ "Dot sect" , "NoPage" , sec3 , [ ] string { "./sect2" } , "" } ,
2018-07-19 10:46:36 -04:00
//{"NoPage", sec3, []string{"sect2"}, ""}, // ISSUE: /sect3 page relative query is resolving to /sect2
2019-09-10 05:26:34 -04:00
{ "Abs, ignore context, home" , page . KindHome , sec3 , [ ] string { "/" } , "home page" } ,
{ "Abs, ignore context, about" , page . KindPage , sec3 , [ ] string { "/about.md" } , "about page" } ,
{ "Abs, ignore context, page in section" , page . KindPage , sec3 , [ ] string { "/sect4/page2.md" } , "Title4_2" } ,
2020-12-02 07:23:25 -05:00
{ "Abs, ignore context, page subsect deep" , page . KindPage , sec3 , [ ] string { "/sect3/subsect/deep.md" } , "deep page" } , // next test depends on this page existing
2019-09-10 05:26:34 -04:00
{ "Abs, ignore context, page deep" , "NoPage" , sec3 , [ ] string { "/subsect/deep.md" } , "" } ,
// Taxonomies
2020-06-16 09:43:50 -04:00
{ "Taxonomy term" , page . KindTaxonomy , nil , [ ] string { "categories" } , "Categories" } ,
{ "Taxonomy" , page . KindTerm , nil , [ ] string { "categories/hugo" , "categories/Hugo" } , "Hugo" } ,
2019-09-10 05:26:34 -04:00
// Bundle variants
{ "Bundle regular" , page . KindPage , nil , [ ] string { "sect3/b1" , "sect3/b1/index.md" , "sect3/b1/index.en.md" } , "b1 bundle" } ,
{ "Bundle index name" , page . KindPage , nil , [ ] string { "sect3/index/index.md" , "sect3/index" } , "index bundle" } ,
2020-05-21 05:25:00 -04:00
// https://github.com/gohugoio/hugo/issues/7301
{ "Section and bundle overlap" , page . KindPage , nil , [ ] string { "section_bundle_overlap_bundle" } , "index overlap bundle" } ,
2017-05-25 05:32:02 -04:00
}
2018-07-19 10:46:36 -04:00
for _ , test := range tests {
2019-09-10 05:26:34 -04:00
c . Run ( test . name , func ( c * qt . C ) {
errorMsg := fmt . Sprintf ( "Test case %v %v -> %s" , test . context , test . pathVariants , test . expectedTitle )
// test legacy public Site.GetPage (which does not support page context relative queries)
if test . context == nil {
for _ , ref := range test . pathVariants {
args := append ( [ ] string { test . kind } , ref )
2023-01-04 12:24:36 -05:00
page , err := s . GetPage ( args ... )
2019-09-10 05:26:34 -04:00
test . check ( page , err , errorMsg , c )
}
}
// test new internal Site.getPageNew
for _ , ref := range test . pathVariants {
page2 , err := s . getPageNew ( test . context , ref )
test . check ( page2 , err , errorMsg , c )
}
} )
}
}
2018-05-29 21:35:27 -04:00
2019-09-10 05:26:34 -04:00
// https://github.com/gohugoio/hugo/issues/6034
func TestGetPageRelative ( t * testing . T ) {
b := newTestSitesBuilder ( t )
for i , section := range [ ] string { "what" , "where" , "who" } {
isDraft := i == 2
b . WithContent (
section + "/_index.md" , fmt . Sprintf ( "---title: %s\n---" , section ) ,
section + "/members.md" , fmt . Sprintf ( "---title: members %s\ndraft: %t\n---" , section , isDraft ) ,
)
2017-05-25 05:32:02 -04:00
}
2019-09-10 05:26:34 -04:00
b . WithTemplates ( "_default/list.html" , `
{ { with . GetPage "members.md" } }
Members : { { . Title } }
{ { else } }
NOT FOUND
{ { end } }
` )
b . Build ( BuildCfg { } )
b . AssertFileContent ( "public/what/index.html" , ` Members: members what ` )
b . AssertFileContent ( "public/where/index.html" , ` Members: members where ` )
b . AssertFileContent ( "public/who/index.html" , ` NOT FOUND ` )
2017-05-25 05:32:02 -04:00
}
2020-03-09 09:01:28 -04:00
// https://github.com/gohugoio/hugo/issues/7016
func TestGetPageMultilingual ( t * testing . T ) {
b := newTestSitesBuilder ( t )
b . WithConfigFile ( "yaml" , `
baseURL : "http://example.org/"
languageCode : "en-us"
defaultContentLanguage : ru
title : "My New Hugo Site"
uglyurls : true
languages :
ru : { }
en : { }
` )
b . WithContent (
"docs/1.md" , "\n---title: p1\n---" ,
"news/1.md" , "\n---title: p1\n---" ,
"news/1.en.md" , "\n---title: p1en\n---" ,
"news/about/1.md" , "\n---title: about1\n---" ,
"news/about/1.en.md" , "\n---title: about1en\n---" ,
)
b . WithTemplates ( "index.html" , `
{ { with site . GetPage "docs/1" } }
Docs p1 : { { . Title } }
{ { else } }
NOT FOUND
{ { end } }
` )
b . Build ( BuildCfg { } )
b . AssertFileContent ( "public/index.html" , ` Docs p1: p1 ` )
b . AssertFileContent ( "public/en/index.html" , ` NOT FOUND ` )
}
func TestShouldDoSimpleLookup ( t * testing . T ) {
c := qt . New ( t )
c . Assert ( shouldDoSimpleLookup ( "foo.md" ) , qt . Equals , true )
c . Assert ( shouldDoSimpleLookup ( "/foo.md" ) , qt . Equals , true )
c . Assert ( shouldDoSimpleLookup ( "./foo.md" ) , qt . Equals , false )
c . Assert ( shouldDoSimpleLookup ( "docs/foo.md" ) , qt . Equals , false )
}
2020-03-16 06:37:57 -04:00
func TestRegularPagesRecursive ( t * testing . T ) {
b := newTestSitesBuilder ( t )
b . WithConfigFile ( "yaml" , `
baseURL : "http://example.org/"
title : "My New Hugo Site"
` )
b . WithContent (
"docs/1.md" , "\n---title: docs1\n---" ,
"docs/sect1/_index.md" , "\n---title: docs_sect1\n---" ,
"docs/sect1/ps1.md" , "\n---title: docs_sect1_ps1\n---" ,
"docs/sect1/ps2.md" , "\n---title: docs_sect1_ps2\n---" ,
"docs/sect1/sect1_s2/_index.md" , "\n---title: docs_sect1_s2\n---" ,
"docs/sect1/sect1_s2/ps2_1.md" , "\n---title: docs_sect1_s2_1\n---" ,
"docs/sect2/_index.md" , "\n---title: docs_sect2\n---" ,
"docs/sect2/ps1.md" , "\n---title: docs_sect2_ps1\n---" ,
"docs/sect2/ps2.md" , "\n---title: docs_sect2_ps2\n---" ,
"news/1.md" , "\n---title: news1\n---" ,
)
b . WithTemplates ( "index.html" , `
{ { $ sect1 := site . GetPage "sect1" } }
Sect1 RegularPagesRecursive : { { range $ sect1 . RegularPagesRecursive } } { { . Kind } } : { { . RelPermalink } } | { { end } } | End .
` )
b . Build ( BuildCfg { } )
b . AssertFileContent ( "public/index.html" , `
Sect1 RegularPagesRecursive : page : / docs / sect1 / ps1 / | page : / docs / sect1 / ps2 / | page : / docs / sect1 / sect1_s2 / ps2_1 / || End .
` )
}