Browse Source

Merge remote-tracking branch 'origin/V20220815' into zouap

tags/v1.22.8.1^2
zouap 3 years ago
parent
commit
c64574c0ac
46 changed files with 3300 additions and 2076 deletions
  1. +1
    -1
      go.mod
  2. +2
    -0
      go.sum
  3. +0
    -15
      models/cloudbrain.go
  4. +16
    -127
      models/cloudbrain_static.go
  5. +12
    -0
      models/repo.go
  6. +1
    -0
      options/locale/locale_en-US.ini
  7. +1
    -0
      options/locale/locale_zh-CN.ini
  8. +37
    -16
      routers/repo/cloudbrain.go
  9. +13
    -12
      routers/repo/grampus.go
  10. +17
    -8
      routers/repo/modelarts.go
  11. +1
    -1
      templates/repo/cloudbrain/benchmark/index.tmpl
  12. +1
    -1
      templates/repo/debugjob/index.tmpl
  13. +1
    -1
      templates/repo/modelarts/inferencejob/index.tmpl
  14. +1
    -1
      templates/repo/modelarts/trainjob/index.tmpl
  15. +66
    -3
      templates/repo/view_file.tmpl
  16. +1
    -1
      vendor/github.com/yuin/goldmark/Makefile
  17. +121
    -14
      vendor/github.com/yuin/goldmark/README.md
  18. +22
    -6
      vendor/github.com/yuin/goldmark/ast/ast.go
  19. +37
    -3
      vendor/github.com/yuin/goldmark/ast/block.go
  20. +2
    -2
      vendor/github.com/yuin/goldmark/ast/inline.go
  21. +27
    -14
      vendor/github.com/yuin/goldmark/extension/ast/footnote.go
  22. +1
    -1
      vendor/github.com/yuin/goldmark/extension/definition_list.go
  23. +386
    -35
      vendor/github.com/yuin/goldmark/extension/footnote.go
  24. +27
    -12
      vendor/github.com/yuin/goldmark/extension/linkify.go
  25. +273
    -40
      vendor/github.com/yuin/goldmark/extension/table.go
  26. +81
    -15
      vendor/github.com/yuin/goldmark/extension/typographer.go
  27. +1
    -1
      vendor/github.com/yuin/goldmark/go.mod
  28. +10
    -1
      vendor/github.com/yuin/goldmark/parser/attribute.go
  29. +5
    -1
      vendor/github.com/yuin/goldmark/parser/atx_heading.go
  30. +21
    -0
      vendor/github.com/yuin/goldmark/parser/code_block.go
  31. +7
    -6
      vendor/github.com/yuin/goldmark/parser/code_span.go
  32. +11
    -15
      vendor/github.com/yuin/goldmark/parser/delimiter.go
  33. +13
    -2
      vendor/github.com/yuin/goldmark/parser/fcode_block.go
  34. +6
    -6
      vendor/github.com/yuin/goldmark/parser/html_block.go
  35. +65
    -43
      vendor/github.com/yuin/goldmark/parser/link.go
  36. +31
    -42
      vendor/github.com/yuin/goldmark/parser/link_ref.go
  37. +46
    -10
      vendor/github.com/yuin/goldmark/parser/list.go
  38. +13
    -8
      vendor/github.com/yuin/goldmark/parser/list_item.go
  39. +41
    -16
      vendor/github.com/yuin/goldmark/parser/parser.go
  40. +81
    -26
      vendor/github.com/yuin/goldmark/parser/raw_html.go
  41. +49
    -11
      vendor/github.com/yuin/goldmark/renderer/html/html.go
  42. +110
    -0
      vendor/github.com/yuin/goldmark/text/reader.go
  43. +1531
    -1488
      vendor/github.com/yuin/goldmark/util/unicode_case_folding.go
  44. +104
    -67
      vendor/github.com/yuin/goldmark/util/util.go
  45. +6
    -3
      vendor/github.com/yuin/goldmark/util/util_unsafe.go
  46. +1
    -1
      vendor/modules.txt

+ 1
- 1
go.mod View File

@@ -120,7 +120,7 @@ require (
github.com/urfave/cli v1.22.1 github.com/urfave/cli v1.22.1
github.com/xanzy/go-gitlab v0.31.0 github.com/xanzy/go-gitlab v0.31.0
github.com/yohcop/openid-go v1.0.0 github.com/yohcop/openid-go v1.0.0
github.com/yuin/goldmark v1.1.30
github.com/yuin/goldmark v1.4.13
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
golang.org/x/mod v0.3.0 // indirect golang.org/x/mod v0.3.0 // indirect


+ 2
- 0
go.sum View File

@@ -804,6 +804,8 @@ github.com/yuin/goldmark v1.1.27 h1:nqDD4MMMQA0lmWq03Z2/myGPYLQoXtmi0rGVs95ntbo=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.30 h1:j4d4Lw3zqZelDhBksEo3BnWg9xhXRQGJPPSL6OApZjI= github.com/yuin/goldmark v1.1.30 h1:j4d4Lw3zqZelDhBksEo3BnWg9xhXRQGJPPSL6OApZjI=
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 h1:gZucqLjL1eDzVWrXj4uiWeMbAopJlBR2mKQAsTGdPwo= github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 h1:gZucqLjL1eDzVWrXj4uiWeMbAopJlBR2mKQAsTGdPwo=
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60/go.mod h1:i9VhcIHN2PxXMbQrKqXNueok6QNONoPjNMoj9MygVL0= github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60/go.mod h1:i9VhcIHN2PxXMbQrKqXNueok6QNONoPjNMoj9MygVL0=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=


+ 0
- 15
models/cloudbrain.go View File

@@ -1680,21 +1680,6 @@ func GetCloudbrainsNeededStopByUserID(userID int64) ([]*Cloudbrain, error) {
return cloudBrains, err return cloudBrains, err
} }


func GetWaittingTop() ([]*CloudbrainInfo, error) {
sess := x.NewSession()
defer sess.Close()
var cond = builder.NewCond()
cond = cond.And(
builder.Eq{"cloudbrain.status": string(JobWaiting)},
)
sess.OrderBy("cloudbrain.created_unix ASC limit 1")
cloudbrains := make([]*CloudbrainInfo, 0, 1)
if err := sess.Table(&Cloudbrain{}).Where(cond).
Find(&cloudbrains); err != nil {
log.Info("find error.")
}
return cloudbrains, nil
}
func GetModelartsReDebugTaskByJobId(jobID string) ([]*Cloudbrain, error) { func GetModelartsReDebugTaskByJobId(jobID string) ([]*Cloudbrain, error) {
sess := x.NewSession() sess := x.NewSession()
defer sess.Close() defer sess.Close()


+ 16
- 127
models/cloudbrain_static.go View File

@@ -36,133 +36,6 @@ type TaskDetail struct {
FlavorName string `json:"FlavorName"` FlavorName string `json:"FlavorName"`
} }


func GetDebugOnePeriodCount(beginTime time.Time, endTime time.Time) (int64, error) {
countSql := "SELECT count(*) FROM " +
"public.cloudbrain where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) +
" and job_type ='" + string(JobTypeDebug) + "'" +
" and type='" + strconv.Itoa(TypeCloudBrainOne) + "'"

return x.SQL(countSql).Count()
}
func GetDebugOnePeriodDuration(beginTime time.Time, endTime time.Time) (int64, error) {
total, err := x.Where("created_unix >= ? And created_unix < ? And job_type = ? And type = ? ", strconv.FormatInt(beginTime.Unix(), 10), strconv.FormatInt(endTime.Unix(), 10), JobTypeDebug, TypeCloudBrainOne).SumInt(&Cloudbrain{}, "duration")
if err != nil {
return 0, err
}

return total, nil
}

func GetTrainOnePeriodCount(beginTime time.Time, endTime time.Time) (int64, error) {
countSql := "SELECT count(*) FROM " +
"public.cloudbrain where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) +
" and job_type ='" + string(JobTypeTrain) + "'" +
" and type='" + strconv.Itoa(TypeCloudBrainOne) + "'"

return x.SQL(countSql).Count()
}
func GetTrainOnePeriodDuration(beginTime time.Time, endTime time.Time) (int64, error) {
total, err := x.Where("created_unix >= ? And created_unix < ? And job_type = ? And type = ? ", strconv.FormatInt(beginTime.Unix(), 10), strconv.FormatInt(endTime.Unix(), 10), JobTypeTrain, TypeCloudBrainOne).SumInt(&Cloudbrain{}, "duration")
if err != nil {
return 0, err
}

return total, nil
}

func GetBenchmarkOnePeriodCount(beginTime time.Time, endTime time.Time) (int64, error) {
countSql := "SELECT count(*) FROM " +
"public.cloudbrain where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) +
" and job_type ='" + string(JobTypeBenchmark) + "'" +
" and type='" + strconv.Itoa(TypeCloudBrainOne) + "'"
return x.SQL(countSql).Count()
}
func GetBenchmarkOnePeriodDuration(beginTime time.Time, endTime time.Time) (int64, error) {
total, err := x.Where("created_unix >= ? And created_unix < ? And job_type = ? And type = ? ", strconv.FormatInt(beginTime.Unix(), 10), strconv.FormatInt(endTime.Unix(), 10), JobTypeBenchmark, TypeCloudBrainOne).SumInt(&Cloudbrain{}, "duration")
if err != nil {
return 0, err
}

return total, nil
}
func GetDebugTwoPeriodCount(beginTime time.Time, endTime time.Time) (int64, error) {
countSql := "SELECT count(*) FROM " +
"public.cloudbrain where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) +
" and job_type ='" + string(JobTypeDebug) + "'" +
" and type='" + strconv.Itoa(TypeCloudBrainTwo) + "'"
return x.SQL(countSql).Count()
}
func GetDebugTwoPeriodDuration(beginTime time.Time, endTime time.Time) (int64, error) {
total, err := x.Where("created_unix >= ? And created_unix < ? And job_type = ? And type = ? ", strconv.FormatInt(beginTime.Unix(), 10), strconv.FormatInt(endTime.Unix(), 10), JobTypeDebug, TypeCloudBrainTwo).SumInt(&Cloudbrain{}, "duration")
if err != nil {
return 0, err
}
return total, nil
}
func GetTrainTwoPeriodCount(beginTime time.Time, endTime time.Time) (int64, error) {
countSql := "SELECT count(*) FROM " +
"public.cloudbrain where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) +
" and job_type ='" + string(JobTypeTrain) + "'" +
" and type='" + strconv.Itoa(TypeCloudBrainTwo) + "'"
return x.SQL(countSql).Count()
}
func GetTrainTwoPeriodDuration(beginTime time.Time, endTime time.Time) (int64, error) {
total, err := x.Where("created_unix >= ? And created_unix < ? And job_type = ? And type = ? ", strconv.FormatInt(beginTime.Unix(), 10), strconv.FormatInt(endTime.Unix(), 10), JobTypeTrain, TypeCloudBrainTwo).SumInt(&Cloudbrain{}, "duration")
if err != nil {
return 0, err
}
return total, nil
}
func GetInferenceTwoPeriodCount(beginTime time.Time, endTime time.Time) (int64, error) {
countSql := "SELECT count(*) FROM " +
"public.cloudbrain where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) +
" and job_type ='" + string(JobTypeInference) + "'" +
" and type='" + strconv.Itoa(TypeCloudBrainTwo) + "'"
return x.SQL(countSql).Count()
}
func GetInferenceTwoPeriodDuration(beginTime time.Time, endTime time.Time) (int64, error) {
total, err := x.Where("created_unix >= ? And created_unix < ? And job_type = ? And type = ? ", strconv.FormatInt(beginTime.Unix(), 10), strconv.FormatInt(endTime.Unix(), 10), JobTypeInference, TypeCloudBrainTwo).SumInt(&Cloudbrain{}, "duration")
if err != nil {
return 0, err
}
return total, nil
}

func GetCloudBrainOnePeriodCount(beginTime time.Time, endTime time.Time) (int64, error) {
countSql := "SELECT count(*) FROM " +
"public.cloudbrain where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) +
" and type='" + strconv.Itoa(TypeCloudBrainOne) + "'"
return x.SQL(countSql).Count()
}
func GetCloudBrainOnePeriodDuration(beginTime time.Time, endTime time.Time) (int64, error) {
total, err := x.Where("created_unix >= ? And created_unix < ? And type = ? ", strconv.FormatInt(beginTime.Unix(), 10), strconv.FormatInt(endTime.Unix(), 10), TypeCloudBrainOne).SumInt(&Cloudbrain{}, "duration")
if err != nil {
return 0, err
}
return total, nil
}
func GetCloudBrainTwoPeriodCount(beginTime time.Time, endTime time.Time) (int64, error) {
countSql := "SELECT count(*) FROM " +
"public.cloudbrain where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) +
" and type='" + strconv.Itoa(TypeCloudBrainTwo) + "'"
return x.SQL(countSql).Count()
}
func GetCloudBrainTwoPeriodDuration(beginTime time.Time, endTime time.Time) (int64, error) {
total, err := x.Where("created_unix >= ? And created_unix < ? And type = ? ", strconv.FormatInt(beginTime.Unix(), 10), strconv.FormatInt(endTime.Unix(), 10), TypeCloudBrainTwo).SumInt(&Cloudbrain{}, "duration")
if err != nil {
return 0, err
}
return total, nil
}

func GetTodayCreatorCount(beginTime time.Time, endTime time.Time) (int64, error) { func GetTodayCreatorCount(beginTime time.Time, endTime time.Time) (int64, error) {
countSql := "SELECT count(distinct user_id) FROM " + countSql := "SELECT count(distinct user_id) FROM " +
"public.cloudbrain where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) + "public.cloudbrain where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) +
@@ -211,6 +84,22 @@ func GetAllStatusCloudBrain() map[string]int {
return cloudBrainStatusResult return cloudBrainStatusResult
} }


func GetWaittingTop() ([]*CloudbrainInfo, error) {
sess := x.NewSession()
defer sess.Close()
var cond = builder.NewCond()
cond = cond.And(
builder.Eq{"cloudbrain.status": string(JobWaiting)},
)
sess.OrderBy("cloudbrain.created_unix ASC limit 10")
cloudbrains := make([]*CloudbrainInfo, 0, 10)
if err := sess.Table(&Cloudbrain{}).Where(cond).
Find(&cloudbrains); err != nil {
log.Info("find error.")
}
return cloudbrains, nil
}

func GetRunningTop() ([]*CloudbrainInfo, error) { func GetRunningTop() ([]*CloudbrainInfo, error) {
sess := x.NewSession() sess := x.NewSession()
defer sess.Close() defer sess.Close()


+ 12
- 0
models/repo.go View File

@@ -2250,6 +2250,18 @@ func CheckRepoStats(ctx context.Context) error {
"UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?",
"repository count 'num_stars'", "repository count 'num_stars'",
}, },
//Repository.NumIssues
{
"SELECT repo.id FROM `repository` repo WHERE repo.num_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_pull=false)",
"UPDATE `repository` SET num_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_pull=false) WHERE id=?",
"repository count 'num_issues'",
},
//Repository.NumPulls
{
"SELECT repo.id FROM `repository` repo WHERE repo.num_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_pull=true)",
"UPDATE `repository` SET num_pulls=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_pull=true) WHERE id=?",
"repository count 'num_pulls'",
},
// Label.NumIssues // Label.NumIssues
{ {
"SELECT label.id FROM `label` WHERE label.num_issues!=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=label.id)", "SELECT label.id FROM `label` WHERE label.num_issues!=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=label.id)",


+ 1
- 0
options/locale/locale_en-US.ini View File

@@ -3141,5 +3141,6 @@ Not_Stopped=The job is not stopped, can not be deleted.
Already_stopped=The job is already stopped. Already_stopped=The job is already stopped.
Stopped_failed=Fail to stop the job, please try again later. Stopped_failed=Fail to stop the job, please try again later.
Stopped_success_update_status_fail=Succeed in stopping th job, but failed to update the job status and duration time. Stopped_success_update_status_fail=Succeed in stopping th job, but failed to update the job status and duration time.
load_code_failed=Fail to load code, please check if the right branch is selected.


error.dataset_select = dataset select error:the count exceed the limit or has same name error.dataset_select = dataset select error:the count exceed the limit or has same name

+ 1
- 0
options/locale/locale_zh-CN.ini View File

@@ -3156,6 +3156,7 @@ Not_Stopped=任务还未终止,不能删除。
Already_stopped=任务已停止。 Already_stopped=任务已停止。
Stopped_failed=任务停止失败,请稍后再试。 Stopped_failed=任务停止失败,请稍后再试。
Stopped_success_update_status_fail=任务停止成功,状态及运行时间更新失败。 Stopped_success_update_status_fail=任务停止成功,状态及运行时间更新失败。
load_code_failed=代码加载失败,请确认选择了正确的分支。




error.dataset_select = 数据集选择错误:数量超过限制或者有同名数据集 error.dataset_select = 数据集选择错误:数量超过限制或者有同名数据集

+ 37
- 16
routers/repo/cloudbrain.go View File

@@ -328,12 +328,12 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) {
if branchName == "" { if branchName == "" {
branchName = cloudbrain.DefaultBranchName branchName = cloudbrain.DefaultBranchName
} }
downloadCode(repo, codePath, branchName)
uploadCodeToMinio(codePath+"/", jobName, cloudbrain.CodeMountPath+"/")
modelPath := setting.JobPath + jobName + cloudbrain.ModelMountPath + "/"
mkModelPath(modelPath)
uploadCodeToMinio(modelPath, jobName, cloudbrain.ModelMountPath+"/")
errStr = loadCodeAndMakeModelPath(repo, codePath, branchName, jobName, cloudbrain.ModelMountPath)
if errStr != "" {
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(ctx.Tr(errStr), tpl, &form)
return
}


commitID, _ := ctx.Repo.GitRepo.GetBranchCommitID(branchName) commitID, _ := ctx.Repo.GitRepo.GetBranchCommitID(branchName)


@@ -378,6 +378,30 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) {
} }
} }


func loadCodeAndMakeModelPath(repo *models.Repository, codePath string, branchName string, jobName string, resultPath string) string {
err := downloadCode(repo, codePath, branchName)
if err != nil {
return "cloudbrain.load_code_failed"
}

err = uploadCodeToMinio(codePath+"/", jobName, cloudbrain.CodeMountPath+"/")
if err != nil {
return "cloudbrain.load_code_failed"
}

modelPath := setting.JobPath + jobName + resultPath + "/"
err = mkModelPath(modelPath)
if err != nil {
return "cloudbrain.load_code_failed"
}
err = uploadCodeToMinio(modelPath, jobName, resultPath+"/")
if err != nil {
return "cloudbrain.load_code_failed"
}

return ""
}

func CloudBrainInferenceJobCreate(ctx *context.Context, form auth.CreateCloudBrainInferencForm) { func CloudBrainInferenceJobCreate(ctx *context.Context, form auth.CreateCloudBrainInferencForm) {
ctx.Data["PageIsCloudBrain"] = true ctx.Data["PageIsCloudBrain"] = true
displayJobName := form.DisplayJobName displayJobName := form.DisplayJobName
@@ -444,11 +468,12 @@ func CloudBrainInferenceJobCreate(ctx *context.Context, form auth.CreateCloudBra
if branchName == "" { if branchName == "" {
branchName = cloudbrain.DefaultBranchName branchName = cloudbrain.DefaultBranchName
} }
downloadCode(repo, codePath, branchName)
uploadCodeToMinio(codePath+"/", jobName, cloudbrain.CodeMountPath+"/")
resultPath := setting.JobPath + jobName + cloudbrain.ResultPath + "/"
mkResultPath(resultPath)
uploadCodeToMinio(resultPath, jobName, cloudbrain.ResultPath+"/")
errStr := loadCodeAndMakeModelPath(repo, codePath, branchName, jobName, cloudbrain.ResultPath)
if errStr != "" {
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(ctx.Tr(errStr), tpl, &form)
return
}


commitID, _ := ctx.Repo.GitRepo.GetBranchCommitID(branchName) commitID, _ := ctx.Repo.GitRepo.GetBranchCommitID(branchName)


@@ -1664,11 +1689,7 @@ func uploadCodeToMinio(codePath, jobName, parentDir string) error {
} }


func mkModelPath(modelPath string) error { func mkModelPath(modelPath string) error {
return mkPathAndReadMeFile(modelPath, "You can put the model file into this directory and download it by the web page.")
}

func mkResultPath(resultPath string) error {
return mkPathAndReadMeFile(resultPath, "You can put the result file into this directory and download it by the web page.")
return mkPathAndReadMeFile(modelPath, "You can put the files into this directory and download the files by the web page.")
} }


func mkPathAndReadMeFile(path string, text string) error { func mkPathAndReadMeFile(path string, text string) error {


+ 13
- 12
routers/repo/grampus.go View File

@@ -45,8 +45,7 @@ func GrampusTrainJobGPUNew(ctx *context.Context) {
ctx.ServerError("get new train-job info failed", err) ctx.ServerError("get new train-job info failed", err)
return return
} }
waitCount := cloudbrain.GetWaitingCloudbrainCount(models.TypeC2Net, models.GPUResource, models.JobTypeTrain)
ctx.Data["WaitCount"] = waitCount

ctx.HTML(http.StatusOK, tplGrampusTrainJobGPUNew) ctx.HTML(http.StatusOK, tplGrampusTrainJobGPUNew)
} }


@@ -57,8 +56,6 @@ func GrampusTrainJobNPUNew(ctx *context.Context) {
ctx.ServerError("get new train-job info failed", err) ctx.ServerError("get new train-job info failed", err)
return return
} }
waitCount := cloudbrain.GetWaitingCloudbrainCount(models.TypeC2Net, models.NPUResource, models.JobTypeTrain)
ctx.Data["WaitCount"] = waitCount
ctx.HTML(200, tplGrampusTrainJobNPUNew) ctx.HTML(200, tplGrampusTrainJobNPUNew)
} }


@@ -131,8 +128,12 @@ func grampusTrainJobNewDataPrepare(ctx *context.Context, processType string) err


if processType == grampus.ProcessorTypeGPU { if processType == grampus.ProcessorTypeGPU {
ctx.Data["datasetType"] = models.TypeCloudBrainOne ctx.Data["datasetType"] = models.TypeCloudBrainOne
waitCount := cloudbrain.GetWaitingCloudbrainCount(models.TypeC2Net, models.GPUResource, models.JobTypeTrain)
ctx.Data["WaitCount"] = waitCount
} else if processType == grampus.ProcessorTypeNPU { } else if processType == grampus.ProcessorTypeNPU {
ctx.Data["datasetType"] = models.TypeCloudBrainTwo ctx.Data["datasetType"] = models.TypeCloudBrainTwo
waitCount := cloudbrain.GetWaitingCloudbrainCount(models.TypeC2Net, models.NPUResource, models.JobTypeTrain)
ctx.Data["WaitCount"] = waitCount
} }


return nil return nil
@@ -280,7 +281,7 @@ func GrampusTrainJobGpuCreate(ctx *context.Context, form auth.CreateGrampusTrain
if err := downloadZipCode(ctx, codeLocalPath, branchName); err != nil { if err := downloadZipCode(ctx, codeLocalPath, branchName); err != nil {
log.Error("downloadZipCode failed, server timed out: %s (%v)", repo.FullName(), err, ctx.Data["MsgID"]) log.Error("downloadZipCode failed, server timed out: %s (%v)", repo.FullName(), err, ctx.Data["MsgID"])
grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeGPU) grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeGPU)
ctx.RenderWithErr("Create task failed, internal error", tplGrampusTrainJobGPUNew, &form)
ctx.RenderWithErr(ctx.Tr("cloudbrain.load_code_failed"), tplGrampusTrainJobGPUNew, &form)
return return
} }


@@ -289,7 +290,7 @@ func GrampusTrainJobGpuCreate(ctx *context.Context, form auth.CreateGrampusTrain
if err := uploadCodeToMinio(codeLocalPath+"/", jobName, cloudbrain.CodeMountPath+"/"); err != nil { if err := uploadCodeToMinio(codeLocalPath+"/", jobName, cloudbrain.CodeMountPath+"/"); err != nil {
log.Error("Failed to uploadCodeToMinio: %s (%v)", repo.FullName(), err, ctx.Data["MsgID"]) log.Error("Failed to uploadCodeToMinio: %s (%v)", repo.FullName(), err, ctx.Data["MsgID"])
grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeGPU) grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeGPU)
ctx.RenderWithErr("Create task failed, internal error", tplGrampusTrainJobGPUNew, &form)
ctx.RenderWithErr(ctx.Tr("cloudbrain.load_code_failed"), tplGrampusTrainJobGPUNew, &form)
return return
} }


@@ -297,7 +298,7 @@ func GrampusTrainJobGpuCreate(ctx *context.Context, form auth.CreateGrampusTrain
if err := mkModelPath(modelPath); err != nil { if err := mkModelPath(modelPath); err != nil {
log.Error("Failed to mkModelPath: %s (%v)", repo.FullName(), err, ctx.Data["MsgID"]) log.Error("Failed to mkModelPath: %s (%v)", repo.FullName(), err, ctx.Data["MsgID"])
grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeGPU) grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeGPU)
ctx.RenderWithErr("Create task failed, internal error", tplGrampusTrainJobGPUNew, &form)
ctx.RenderWithErr(ctx.Tr("cloudbrain.load_code_failed"), tplGrampusTrainJobGPUNew, &form)
return return
} }


@@ -305,7 +306,7 @@ func GrampusTrainJobGpuCreate(ctx *context.Context, form auth.CreateGrampusTrain
if err := uploadCodeToMinio(modelPath, jobName, cloudbrain.ModelMountPath+"/"); err != nil { if err := uploadCodeToMinio(modelPath, jobName, cloudbrain.ModelMountPath+"/"); err != nil {
log.Error("Failed to uploadCodeToMinio: %s (%v)", repo.FullName(), err, ctx.Data["MsgID"]) log.Error("Failed to uploadCodeToMinio: %s (%v)", repo.FullName(), err, ctx.Data["MsgID"])
grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeGPU) grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeGPU)
ctx.RenderWithErr("Create task failed, internal error", tplGrampusTrainJobGPUNew, &form)
ctx.RenderWithErr(ctx.Tr("cloudbrain.load_code_failed"), tplGrampusTrainJobGPUNew, &form)
return return
} }


@@ -464,22 +465,22 @@ func GrampusTrainJobNpuCreate(ctx *context.Context, form auth.CreateGrampusTrain
if err := downloadZipCode(ctx, codeLocalPath, branchName); err != nil { if err := downloadZipCode(ctx, codeLocalPath, branchName); err != nil {
log.Error("downloadZipCode failed, server timed out: %s (%v)", repo.FullName(), err) log.Error("downloadZipCode failed, server timed out: %s (%v)", repo.FullName(), err)
grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeNPU) grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeNPU)
ctx.RenderWithErr("Create task failed, server timed out", tplGrampusTrainJobNPUNew, &form)
ctx.RenderWithErr(ctx.Tr("cloudbrain.load_code_failed"), tplGrampusTrainJobNPUNew, &form)
return return
} }


//todo: upload code (send to file_server todo this work?) //todo: upload code (send to file_server todo this work?)
if err := obsMkdir(setting.CodePathPrefix + jobName + modelarts.OutputPath); err != nil { if err := obsMkdir(setting.CodePathPrefix + jobName + modelarts.OutputPath); err != nil {
log.Error("Failed to obsMkdir_output: %s (%v)", repo.FullName(), err) log.Error("Failed to obsMkdir_output: %s (%v)", repo.FullName(), err)
grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeGPU)
ctx.RenderWithErr("Failed to obsMkdir_output", tplGrampusTrainJobNPUNew, &form)
grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeNPU)
ctx.RenderWithErr(ctx.Tr("cloudbrain.load_code_failed"), tplGrampusTrainJobNPUNew, &form)
return return
} }


if err := uploadCodeToObs(codeLocalPath, jobName, ""); err != nil { if err := uploadCodeToObs(codeLocalPath, jobName, ""); err != nil {
log.Error("Failed to uploadCodeToObs: %s (%v)", repo.FullName(), err) log.Error("Failed to uploadCodeToObs: %s (%v)", repo.FullName(), err)
grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeNPU) grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeNPU)
ctx.RenderWithErr("Failed to uploadCodeToObs", tplGrampusTrainJobNPUNew, &form)
ctx.RenderWithErr(ctx.Tr("cloudbrain.load_code_failed"), tplGrampusTrainJobNPUNew, &form)
return return
} }




+ 17
- 8
routers/repo/modelarts.go View File

@@ -119,8 +119,7 @@ func MustEnableModelArts(ctx *context.Context) {


func NotebookNew(ctx *context.Context) { func NotebookNew(ctx *context.Context) {
notebookNewDataPrepare(ctx) notebookNewDataPrepare(ctx)
waitCount := cloudbrain.GetWaitingCloudbrainCount(models.TypeCloudBrainTwo, "")
ctx.Data["WaitCount"] = waitCount

ctx.HTML(200, tplModelArtsNotebookNew) ctx.HTML(200, tplModelArtsNotebookNew)
} }


@@ -150,6 +149,9 @@ func notebookNewDataPrepare(ctx *context.Context) error {


ctx.Data["datasetType"] = models.TypeCloudBrainTwo ctx.Data["datasetType"] = models.TypeCloudBrainTwo


waitCount := cloudbrain.GetWaitingCloudbrainCount(models.TypeCloudBrainTwo, "")
ctx.Data["WaitCount"] = waitCount

return nil return nil
} }


@@ -676,8 +678,6 @@ func TrainJobNew(ctx *context.Context) {
ctx.ServerError("get new train-job info failed", err) ctx.ServerError("get new train-job info failed", err)
return return
} }
waitCount := cloudbrain.GetWaitingCloudbrainCount(models.TypeCloudBrainTwo, "")
ctx.Data["WaitCount"] = waitCount
ctx.HTML(200, tplModelArtsTrainJobNew) ctx.HTML(200, tplModelArtsTrainJobNew)
} }


@@ -747,6 +747,8 @@ func trainJobNewDataPrepare(ctx *context.Context) error {
} }
ctx.Data["config_list"] = configList.ParaConfigs ctx.Data["config_list"] = configList.ParaConfigs
ctx.Data["datasetType"] = models.TypeCloudBrainTwo ctx.Data["datasetType"] = models.TypeCloudBrainTwo
waitCount := cloudbrain.GetWaitingCloudbrainCount(models.TypeCloudBrainTwo, "")
ctx.Data["WaitCount"] = waitCount


return nil return nil
} }
@@ -863,6 +865,8 @@ func trainJobErrorNewDataPrepare(ctx *context.Context, form auth.CreateModelArts
ctx.Data["dataset_name"] = datasetNames ctx.Data["dataset_name"] = datasetNames
ctx.Data["branch_name"] = form.BranchName ctx.Data["branch_name"] = form.BranchName
ctx.Data["datasetType"] = models.TypeCloudBrainTwo ctx.Data["datasetType"] = models.TypeCloudBrainTwo
waitCount := cloudbrain.GetWaitingCloudbrainCount(models.TypeCloudBrainTwo, "")
ctx.Data["WaitCount"] = waitCount


return nil return nil
} }
@@ -874,8 +878,6 @@ func TrainJobNewVersion(ctx *context.Context) {
ctx.ServerError("get new train-job info failed", err) ctx.ServerError("get new train-job info failed", err)
return return
} }
waitCount := cloudbrain.GetWaitingCloudbrainCount(models.TypeCloudBrainTwo, "")
ctx.Data["WaitCount"] = waitCount
ctx.HTML(200, tplModelArtsTrainJobVersionNew) ctx.HTML(200, tplModelArtsTrainJobVersionNew)
} }


@@ -968,6 +970,8 @@ func trainJobNewVersionDataPrepare(ctx *context.Context) error {
return err return err
} }
ctx.Data["config_list"] = configList.ParaConfigs ctx.Data["config_list"] = configList.ParaConfigs
waitCount := cloudbrain.GetWaitingCloudbrainCount(models.TypeCloudBrainTwo, "")
ctx.Data["WaitCount"] = waitCount


return nil return nil
} }
@@ -1059,6 +1063,8 @@ func versionErrorDataPrepare(ctx *context.Context, form auth.CreateModelArtsTrai
} }
ctx.Data["config_list"] = configList.ParaConfigs ctx.Data["config_list"] = configList.ParaConfigs
ctx.Data["datasetType"] = models.TypeCloudBrainTwo ctx.Data["datasetType"] = models.TypeCloudBrainTwo
waitCount := cloudbrain.GetWaitingCloudbrainCount(models.TypeCloudBrainTwo, "")
ctx.Data["WaitCount"] = waitCount


return nil return nil
} }
@@ -2324,8 +2330,7 @@ func InferenceJobNew(ctx *context.Context) {
ctx.ServerError("get new inference-job info failed", err) ctx.ServerError("get new inference-job info failed", err)
return return
} }
waitCount := cloudbrain.GetWaitingCloudbrainCount(models.TypeCloudBrainTwo, "")
ctx.Data["WaitCount"] = waitCount

ctx.HTML(200, tplModelArtsInferenceJobNew) ctx.HTML(200, tplModelArtsInferenceJobNew)
} }
func inferenceJobNewDataPrepare(ctx *context.Context) error { func inferenceJobNewDataPrepare(ctx *context.Context) error {
@@ -2396,6 +2401,8 @@ func inferenceJobNewDataPrepare(ctx *context.Context) error {
}) })
ctx.Data["MODEL_COUNT"] = model_count ctx.Data["MODEL_COUNT"] = model_count
ctx.Data["datasetType"] = models.TypeCloudBrainTwo ctx.Data["datasetType"] = models.TypeCloudBrainTwo
waitCount := cloudbrain.GetWaitingCloudbrainCount(models.TypeCloudBrainTwo, "")
ctx.Data["WaitCount"] = waitCount


return nil return nil
} }
@@ -2469,6 +2476,8 @@ func inferenceJobErrorNewDataPrepare(ctx *context.Context, form auth.CreateModel
ctx.Data["ckpt_name"] = form.CkptName ctx.Data["ckpt_name"] = form.CkptName
ctx.Data["train_url"] = form.TrainUrl ctx.Data["train_url"] = form.TrainUrl
ctx.Data["datasetType"] = models.TypeCloudBrainTwo ctx.Data["datasetType"] = models.TypeCloudBrainTwo
waitCount := cloudbrain.GetWaitingCloudbrainCount(models.TypeCloudBrainTwo, "")
ctx.Data["WaitCount"] = waitCount


return nil return nil
} }


+ 1
- 1
templates/repo/cloudbrain/benchmark/index.tmpl View File

@@ -30,7 +30,7 @@
<div class="column"> <div class="column">
<div class="ui blue small menu compact selectcloudbrain"> <div class="ui blue small menu compact selectcloudbrain">
<a class="item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a> <a class="item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a>
<a class="item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a>
<a class="item" href="{{.RepoLink}}/modelarts/train-job?listType=all">{{$.i18n.Tr "repo.modelarts.train_job"}}</a>
<a class="item" href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a> <a class="item" href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a>
<a class="active item" href="{{.RepoLink}}/cloudbrain/benchmark">{{$.i18n.Tr "repo.modelarts.evaluate_job"}}</a> <a class="active item" href="{{.RepoLink}}/cloudbrain/benchmark">{{$.i18n.Tr "repo.modelarts.evaluate_job"}}</a>
</div> </div>


+ 1
- 1
templates/repo/debugjob/index.tmpl View File

@@ -230,7 +230,7 @@
<a class="active item" <a class="active item"
href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a> href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a>
<a class="item" <a class="item"
href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a>
href="{{.RepoLink}}/modelarts/train-job?listType=all">{{$.i18n.Tr "repo.modelarts.train_job"}}</a>
<a class="item" <a class="item"
href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a> href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a>
<a class="item" <a class="item"


+ 1
- 1
templates/repo/modelarts/inferencejob/index.tmpl View File

@@ -33,7 +33,7 @@
<div class="column"> <div class="column">
<div class="ui blue small menu compact selectcloudbrain"> <div class="ui blue small menu compact selectcloudbrain">
<a class="item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a> <a class="item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a>
<a class="item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a>
<a class="item" href="{{.RepoLink}}/modelarts/train-job?listType=all">{{$.i18n.Tr "repo.modelarts.train_job"}}</a>
<a class="active item" href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a> <a class="active item" href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a>
<a class="item" href="{{.RepoLink}}/cloudbrain/benchmark">{{$.i18n.Tr "repo.modelarts.evaluate_job"}}</a> <a class="item" href="{{.RepoLink}}/cloudbrain/benchmark">{{$.i18n.Tr "repo.modelarts.evaluate_job"}}</a>
</div> </div>


+ 1
- 1
templates/repo/modelarts/trainjob/index.tmpl View File

@@ -33,7 +33,7 @@
<div class="column"> <div class="column">
<div class="ui blue small menu compact selectcloudbrain"> <div class="ui blue small menu compact selectcloudbrain">
<a class="item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a> <a class="item" href="{{.RepoLink}}/debugjob?debugListType=all">{{$.i18n.Tr "repo.modelarts.notebook"}}</a>
<a class="active item" href="{{.RepoLink}}/modelarts/train-job">{{$.i18n.Tr "repo.modelarts.train_job"}}</a>
<a class="active item" href="{{.RepoLink}}/modelarts/train-job?modelarts/train-job?listType=all">{{$.i18n.Tr "repo.modelarts.train_job"}}</a>
<a class="item" href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a> <a class="item" href="{{.RepoLink}}/modelarts/inference-job">{{$.i18n.Tr "repo.modelarts.infer_job"}}</a>
<a class="item" href="{{.RepoLink}}/cloudbrain/benchmark">{{$.i18n.Tr "repo.modelarts.evaluate_job"}}</a> <a class="item" href="{{.RepoLink}}/cloudbrain/benchmark">{{$.i18n.Tr "repo.modelarts.evaluate_job"}}</a>
</div> </div>


+ 66
- 3
templates/repo/view_file.tmpl View File

@@ -133,16 +133,79 @@ function submitDeleteForm() {
$("#delete-file-form").submit() $("#delete-file-form").submit()
} }
} }


const baseUrls = {};
const justDomain = /^[^:]+:\/*[^/]*$/;
const protocol = /^([^:]+:)[\s\S]*$/;
const domain = /^([^:]+:\/*[^/]*)[\s\S]*$/;
const originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
function rtrim(str, c, invert) {
const l = str.length;
if (l === 0) {
return '';
}
let suffLen = 0;
while (suffLen < l) {
const currChar = str.charAt(l - suffLen - 1);
if (currChar === c && !invert) {
suffLen++;
} else if (currChar !== c && invert) {
suffLen++;
} else {
break;
}
}

return str.slice(0, l - suffLen);
}
function resolveUrl(base, href) {
if (!baseUrls[' ' + base]) {
if (justDomain.test(base)) {
baseUrls[' ' + base] = base + '/';
} else {
baseUrls[' ' + base] = rtrim(base, '/', true);
}
}
base = baseUrls[' ' + base];
const relativeBase = base.indexOf(':') === -1;

if (href.substring(0, 2) === '//') {
if (relativeBase) {
return href;
}
return base.replace(protocol, '$1') + href;
} else if (href.charAt(0) === '/') {
if (relativeBase) {
return href;
}
return base.replace(domain, '$1') + href;
} else {
return base + href;
}
}
function showNoteBook(){ function showNoteBook(){
var isNoteBook = {{.IsNoteBook}}
var isNoteBook = {{.IsNoteBook}};
if (isNoteBook) { if (isNoteBook) {
var jsonStr = "{{.FileContent}}"
var jsonStr = "{{.FileContent}}";
var baseUrl={{.FileParentURL}};
nb.markdown.setOptions({ nb.markdown.setOptions({
baseUrl: {{.FileParentURL}}
baseUrl: baseUrl
}); });
var notebook = nb.parse(JSON.parse(jsonStr)); var notebook = nb.parse(JSON.parse(jsonStr));
var rendered = notebook.render(); var rendered = notebook.render();
$("#notebook").append(rendered); $("#notebook").append(rendered);

$("#notebook img").each(function(){

var oldSrc = $(this).attr('src');
if (!originIndependentUrl.test(oldSrc)){
var newSrc=resolveUrl(baseUrl,oldSrc);
$(this).attr('src', newSrc);

}

});
Prism.highlightAll(); Prism.highlightAll();
} }
} }


+ 1
- 1
vendor/github.com/yuin/goldmark/Makefile View File

@@ -12,5 +12,5 @@ fuzz:
rm -rf ./fuzz/crashers rm -rf ./fuzz/crashers
rm -rf ./fuzz/suppressions rm -rf ./fuzz/suppressions
rm -f ./fuzz/fuzz-fuzz.zip rm -f ./fuzz/fuzz-fuzz.zip
cd ./fuzz && go-fuzz-build
cd ./fuzz && GO111MODULE=off go-fuzz-build
cd ./fuzz && go-fuzz cd ./fuzz && go-fuzz

+ 121
- 14
vendor/github.com/yuin/goldmark/README.md View File

@@ -1,14 +1,14 @@
goldmark goldmark
========================================== ==========================================


[![http://godoc.org/github.com/yuin/goldmark](https://godoc.org/github.com/yuin/goldmark?status.svg)](http://godoc.org/github.com/yuin/goldmark)
[![https://pkg.go.dev/github.com/yuin/goldmark](https://pkg.go.dev/badge/github.com/yuin/goldmark.svg)](https://pkg.go.dev/github.com/yuin/goldmark)
[![https://github.com/yuin/goldmark/actions?query=workflow:test](https://github.com/yuin/goldmark/workflows/test/badge.svg?branch=master&event=push)](https://github.com/yuin/goldmark/actions?query=workflow:test) [![https://github.com/yuin/goldmark/actions?query=workflow:test](https://github.com/yuin/goldmark/workflows/test/badge.svg?branch=master&event=push)](https://github.com/yuin/goldmark/actions?query=workflow:test)
[![https://coveralls.io/github/yuin/goldmark](https://coveralls.io/repos/github/yuin/goldmark/badge.svg?branch=master)](https://coveralls.io/github/yuin/goldmark) [![https://coveralls.io/github/yuin/goldmark](https://coveralls.io/repos/github/yuin/goldmark/badge.svg?branch=master)](https://coveralls.io/github/yuin/goldmark)
[![https://goreportcard.com/report/github.com/yuin/goldmark](https://goreportcard.com/badge/github.com/yuin/goldmark)](https://goreportcard.com/report/github.com/yuin/goldmark) [![https://goreportcard.com/report/github.com/yuin/goldmark](https://goreportcard.com/badge/github.com/yuin/goldmark)](https://goreportcard.com/report/github.com/yuin/goldmark)


> A Markdown parser written in Go. Easy to extend, standards-compliant, well-structured. > A Markdown parser written in Go. Easy to extend, standards-compliant, well-structured.


goldmark is compliant with CommonMark 0.29.
goldmark is compliant with CommonMark 0.30.


Motivation Motivation
---------------------- ----------------------
@@ -173,6 +173,7 @@ Parser and Renderer options
- This extension enables Table, Strikethrough, Linkify and TaskList. - This extension enables Table, Strikethrough, Linkify and TaskList.
- This extension does not filter tags defined in [6.11: Disallowed Raw HTML (extension)](https://github.github.com/gfm/#disallowed-raw-html-extension-). - This extension does not filter tags defined in [6.11: Disallowed Raw HTML (extension)](https://github.github.com/gfm/#disallowed-raw-html-extension-).
If you need to filter HTML tags, see [Security](#security). If you need to filter HTML tags, see [Security](#security).
- If you need to parse github emojis, you can use [goldmark-emoji](https://github.com/yuin/goldmark-emoji) extension.
- `extension.DefinitionList` - `extension.DefinitionList`
- [PHP Markdown Extra: Definition lists](https://michelf.ca/projects/php-markdown/extra/#def-list) - [PHP Markdown Extra: Definition lists](https://michelf.ca/projects/php-markdown/extra/#def-list)
- `extension.Footnote` - `extension.Footnote`
@@ -203,6 +204,18 @@ heading {#id .className attrName=attrValue}
============ ============
``` ```


### Table extension
The Table extension implements [Table(extension)](https://github.github.com/gfm/#tables-extension-), as
defined in [GitHub Flavored Markdown Spec](https://github.github.com/gfm/).

Specs are defined for XHTML, so specs use some deprecated attributes for HTML5.

You can override alignment rendering method via options.

| Functional option | Type | Description |
| ----------------- | ---- | ----------- |
| `extension.WithTableCellAlignMethod` | `extension.TableCellAlignMethod` | Option indicates how are table cells aligned. |

### Typographer extension ### Typographer extension


The Typographer extension translates plain ASCII punctuation characters into typographic-punctuation HTML entities. The Typographer extension translates plain ASCII punctuation characters into typographic-punctuation HTML entities.
@@ -219,7 +232,7 @@ Default substitutions are:
| `<<` | `&laquo;` | | `<<` | `&laquo;` |
| `>>` | `&raquo;` | | `>>` | `&raquo;` |


You can override the defualt substitutions via `extensions.WithTypographicSubstitutions`:
You can override the default substitutions via `extensions.WithTypographicSubstitutions`:


```go ```go
markdown := goldmark.New( markdown := goldmark.New(
@@ -267,13 +280,96 @@ markdown := goldmark.New(
[]byte("https:"), []byte("https:"),
}), }),
extension.WithLinkifyURLRegexp( extension.WithLinkifyURLRegexp(
xurls.Strict(),
xurls.Strict,
), ),
), ),
), ),
) )
``` ```


### Footnotes extension

The Footnote extension implements [PHP Markdown Extra: Footnotes](https://michelf.ca/projects/php-markdown/extra/#footnotes).

This extension has some options:

| Functional option | Type | Description |
| ----------------- | ---- | ----------- |
| `extension.WithFootnoteIDPrefix` | `[]byte` | a prefix for the id attributes.|
| `extension.WithFootnoteIDPrefixFunction` | `func(gast.Node) []byte` | a function that determines the id attribute for given Node.|
| `extension.WithFootnoteLinkTitle` | `[]byte` | an optional title attribute for footnote links.|
| `extension.WithFootnoteBacklinkTitle` | `[]byte` | an optional title attribute for footnote backlinks. |
| `extension.WithFootnoteLinkClass` | `[]byte` | a class for footnote links. This defaults to `footnote-ref`. |
| `extension.WithFootnoteBacklinkClass` | `[]byte` | a class for footnote backlinks. This defaults to `footnote-backref`. |
| `extension.WithFootnoteBacklinkHTML` | `[]byte` | a class for footnote backlinks. This defaults to `&#x21a9;&#xfe0e;`. |

Some options can have special substitutions. Occurrences of “^^” in the string will be replaced by the corresponding footnote number in the HTML output. Occurrences of “%%” will be replaced by a number for the reference (footnotes can have multiple references).

`extension.WithFootnoteIDPrefix` and `extension.WithFootnoteIDPrefixFunction` are useful if you have multiple Markdown documents displayed inside one HTML document to avoid footnote ids to clash each other.

`extension.WithFootnoteIDPrefix` sets fixed id prefix, so you may write codes like the following:

```go
for _, path := range files {
source := readAll(path)
prefix := getPrefix(path)

markdown := goldmark.New(
goldmark.WithExtensions(
NewFootnote(
WithFootnoteIDPrefix([]byte(path)),
),
),
)
var b bytes.Buffer
err := markdown.Convert(source, &b)
if err != nil {
t.Error(err.Error())
}
}
```

`extension.WithFootnoteIDPrefixFunction` determines an id prefix by calling given function, so you may write codes like the following:

```go
markdown := goldmark.New(
goldmark.WithExtensions(
NewFootnote(
WithFootnoteIDPrefixFunction(func(n gast.Node) []byte {
v, ok := n.OwnerDocument().Meta()["footnote-prefix"]
if ok {
return util.StringToReadOnlyBytes(v.(string))
}
return nil
}),
),
),
)

for _, path := range files {
source := readAll(path)
var b bytes.Buffer

doc := markdown.Parser().Parse(text.NewReader(source))
doc.Meta()["footnote-prefix"] = getPrefix(path)
err := markdown.Renderer().Render(&b, source, doc)
}
```

You can use [goldmark-meta](https://github.com/yuin/goldmark-meta) to define a id prefix in the markdown document:


```markdown
---
title: document title
slug: article1
footnote-prefix: article1
---

# My article

```
Security Security
-------------------- --------------------
By default, goldmark does not render raw HTML or potentially-dangerous URLs. By default, goldmark does not render raw HTML or potentially-dangerous URLs.
@@ -291,28 +387,29 @@ blackfriday v2 seems to be the fastest, but as it is not CommonMark compliant, i
goldmark, meanwhile, builds a clean, extensible AST structure, achieves full compliance with goldmark, meanwhile, builds a clean, extensible AST structure, achieves full compliance with
CommonMark, and consumes less memory, all while being reasonably fast. CommonMark, and consumes less memory, all while being reasonably fast.


- MBP 2019 13″(i5, 16GB), Go1.17

``` ```
goos: darwin
goarch: amd64
BenchmarkMarkdown/Blackfriday-v2-12 326 3465240 ns/op 3298861 B/op 20047 allocs/op
BenchmarkMarkdown/GoldMark-12 303 3927494 ns/op 2574809 B/op 13853 allocs/op
BenchmarkMarkdown/CommonMark-12 244 4900853 ns/op 2753851 B/op 20527 allocs/op
BenchmarkMarkdown/Lute-12 130 9195245 ns/op 9175030 B/op 123534 allocs/op
BenchmarkMarkdown/GoMarkdown-12 9 113541994 ns/op 2187472 B/op 22173 allocs/op
BenchmarkMarkdown/Blackfriday-v2-8 302 3743747 ns/op 3290445 B/op 20050 allocs/op
BenchmarkMarkdown/GoldMark-8 280 4200974 ns/op 2559738 B/op 13435 allocs/op
BenchmarkMarkdown/CommonMark-8 226 5283686 ns/op 2702490 B/op 20792 allocs/op
BenchmarkMarkdown/Lute-8 12 92652857 ns/op 10602649 B/op 40555 allocs/op
BenchmarkMarkdown/GoMarkdown-8 13 81380167 ns/op 2245002 B/op 22889 allocs/op
``` ```


### against cmark (CommonMark reference implementation written in C) ### against cmark (CommonMark reference implementation written in C)


- MBP 2019 13″(i5, 16GB), Go1.17

``` ```
----------- cmark ----------- ----------- cmark -----------
file: _data.md file: _data.md
iteration: 50 iteration: 50
average: 0.0037760639 sec
go run ./goldmark_benchmark.go
average: 0.0044073057 sec
------- goldmark ------- ------- goldmark -------
file: _data.md file: _data.md
iteration: 50 iteration: 50
average: 0.0040964230 sec
average: 0.0041611990 sec
``` ```


As you can see, goldmark's performance is on par with cmark's. As you can see, goldmark's performance is on par with cmark's.
@@ -324,7 +421,17 @@ Extensions
extension for the goldmark Markdown parser. extension for the goldmark Markdown parser.
- [goldmark-highlighting](https://github.com/yuin/goldmark-highlighting): A syntax-highlighting extension - [goldmark-highlighting](https://github.com/yuin/goldmark-highlighting): A syntax-highlighting extension
for the goldmark markdown parser. for the goldmark markdown parser.
- [goldmark-emoji](https://github.com/yuin/goldmark-emoji): An emoji
extension for the goldmark Markdown parser.
- [goldmark-mathjax](https://github.com/litao91/goldmark-mathjax): Mathjax support for the goldmark markdown parser - [goldmark-mathjax](https://github.com/litao91/goldmark-mathjax): Mathjax support for the goldmark markdown parser
- [goldmark-pdf](https://github.com/stephenafamo/goldmark-pdf): A PDF renderer that can be passed to `goldmark.WithRenderer()`.
- [goldmark-hashtag](https://github.com/abhinav/goldmark-hashtag): Adds support for `#hashtag`-based tagging to goldmark.
- [goldmark-wikilink](https://github.com/abhinav/goldmark-wikilink): Adds support for `[[wiki]]`-style links to goldmark.
- [goldmark-toc](https://github.com/abhinav/goldmark-toc): Adds support for generating tables-of-contents for goldmark documents.
- [goldmark-mermaid](https://github.com/abhinav/goldmark-mermaid): Adds support for rendering [Mermaid](https://mermaid-js.github.io/mermaid/) diagrams in goldmark documents.
- [goldmark-pikchr](https://github.com/jchenry/goldmark-pikchr): Adds support for rendering [Pikchr](https://pikchr.org/home/doc/trunk/homepage.md) diagrams in goldmark documents.
- [goldmark-embed](https://github.com/13rac1/goldmark-embed): Adds support for rendering embeds from YouTube links.



goldmark internal(for extension developers) goldmark internal(for extension developers)
---------------------------------------------- ----------------------------------------------


+ 22
- 6
vendor/github.com/yuin/goldmark/ast/ast.go View File

@@ -45,11 +45,6 @@ type Attribute struct {
Value interface{} Value interface{}
} }


var attrNameIDS = []byte("#")
var attrNameID = []byte("id")
var attrNameClassS = []byte(".")
var attrNameClass = []byte("class")

// A Node interface defines basic AST node functionalities. // A Node interface defines basic AST node functionalities.
type Node interface { type Node interface {
// Type returns a type of this node. // Type returns a type of this node.
@@ -116,6 +111,11 @@ type Node interface {
// tail of the children. // tail of the children.
InsertAfter(self, v1, insertee Node) InsertAfter(self, v1, insertee Node)


// OwnerDocument returns this node's owner document.
// If this node is not a child of the Document node, OwnerDocument
// returns nil.
OwnerDocument() *Document

// Dump dumps an AST tree structure to stdout. // Dump dumps an AST tree structure to stdout.
// This function completely aimed for debugging. // This function completely aimed for debugging.
// level is a indent level. Implementer should indent informations with // level is a indent level. Implementer should indent informations with
@@ -169,7 +169,7 @@ type Node interface {
RemoveAttributes() RemoveAttributes()
} }


// A BaseNode struct implements the Node interface.
// A BaseNode struct implements the Node interface partialliy.
type BaseNode struct { type BaseNode struct {
firstChild Node firstChild Node
lastChild Node lastChild Node
@@ -358,6 +358,22 @@ func (n *BaseNode) InsertBefore(self, v1, insertee Node) {
} }
} }


// OwnerDocument implements Node.OwnerDocument
func (n *BaseNode) OwnerDocument() *Document {
d := n.Parent()
for {
p := d.Parent()
if p == nil {
if v, ok := d.(*Document); ok {
return v
}
break
}
d = p
}
return nil
}

// Text implements Node.Text . // Text implements Node.Text .
func (n *BaseNode) Text(source []byte) []byte { func (n *BaseNode) Text(source []byte) []byte {
var buf bytes.Buffer var buf bytes.Buffer


+ 37
- 3
vendor/github.com/yuin/goldmark/ast/block.go View File

@@ -7,7 +7,7 @@ import (
textm "github.com/yuin/goldmark/text" textm "github.com/yuin/goldmark/text"
) )


// A BaseBlock struct implements the Node interface.
// A BaseBlock struct implements the Node interface partialliy.
type BaseBlock struct { type BaseBlock struct {
BaseNode BaseNode
blankPreviousLines bool blankPreviousLines bool
@@ -50,6 +50,8 @@ func (b *BaseBlock) SetLines(v *textm.Segments) {
// A Document struct is a root node of Markdown text. // A Document struct is a root node of Markdown text.
type Document struct { type Document struct {
BaseBlock BaseBlock

meta map[string]interface{}
} }


// KindDocument is a NodeKind of the Document node. // KindDocument is a NodeKind of the Document node.
@@ -70,10 +72,42 @@ func (n *Document) Kind() NodeKind {
return KindDocument return KindDocument
} }


// OwnerDocument implements Node.OwnerDocument
func (n *Document) OwnerDocument() *Document {
return n
}

// Meta returns metadata of this document.
func (n *Document) Meta() map[string]interface{} {
if n.meta == nil {
n.meta = map[string]interface{}{}
}
return n.meta
}

// SetMeta sets given metadata to this document.
func (n *Document) SetMeta(meta map[string]interface{}) {
if n.meta == nil {
n.meta = map[string]interface{}{}
}
for k, v := range meta {
n.meta[k] = v
}
}

// AddMeta adds given metadata to this document.
func (n *Document) AddMeta(key string, value interface{}) {
if n.meta == nil {
n.meta = map[string]interface{}{}
}
n.meta[key] = value
}

// NewDocument returns a new Document node. // NewDocument returns a new Document node.
func NewDocument() *Document { func NewDocument() *Document {
return &Document{ return &Document{
BaseBlock: BaseBlock{}, BaseBlock: BaseBlock{},
meta: nil,
} }
} }


@@ -311,7 +345,7 @@ type List struct {
Marker byte Marker byte


// IsTight is a true if this list is a 'tight' list. // IsTight is a true if this list is a 'tight' list.
// See https://spec.commonmark.org/0.29/#loose for details.
// See https://spec.commonmark.org/0.30/#loose for details.
IsTight bool IsTight bool


// Start is an initial number of this ordered list. // Start is an initial number of this ordered list.
@@ -393,7 +427,7 @@ func NewListItem(offset int) *ListItem {
} }


// HTMLBlockType represents kinds of an html blocks. // HTMLBlockType represents kinds of an html blocks.
// See https://spec.commonmark.org/0.29/#html-blocks
// See https://spec.commonmark.org/0.30/#html-blocks
type HTMLBlockType int type HTMLBlockType int


const ( const (


+ 2
- 2
vendor/github.com/yuin/goldmark/ast/inline.go View File

@@ -8,7 +8,7 @@ import (
"github.com/yuin/goldmark/util" "github.com/yuin/goldmark/util"
) )


// A BaseInline struct implements the Node interface.
// A BaseInline struct implements the Node interface partialliy.
type BaseInline struct { type BaseInline struct {
BaseNode BaseNode
} }
@@ -111,7 +111,7 @@ func (n *Text) SetRaw(v bool) {
} }


// HardLineBreak returns true if this node ends with a hard line break. // HardLineBreak returns true if this node ends with a hard line break.
// See https://spec.commonmark.org/0.29/#hard-line-breaks for details.
// See https://spec.commonmark.org/0.30/#hard-line-breaks for details.
func (n *Text) HardLineBreak() bool { func (n *Text) HardLineBreak() bool {
return n.flags&textHardLineBreak != 0 return n.flags&textHardLineBreak != 0
} }


+ 27
- 14
vendor/github.com/yuin/goldmark/extension/ast/footnote.go View File

@@ -2,6 +2,7 @@ package ast


import ( import (
"fmt" "fmt"

gast "github.com/yuin/goldmark/ast" gast "github.com/yuin/goldmark/ast"
) )


@@ -9,13 +10,17 @@ import (
// (PHP Markdown Extra) text. // (PHP Markdown Extra) text.
type FootnoteLink struct { type FootnoteLink struct {
gast.BaseInline gast.BaseInline
Index int
Index int
RefCount int
RefIndex int
} }


// Dump implements Node.Dump. // Dump implements Node.Dump.
func (n *FootnoteLink) Dump(source []byte, level int) { func (n *FootnoteLink) Dump(source []byte, level int) {
m := map[string]string{} m := map[string]string{}
m["Index"] = fmt.Sprintf("%v", n.Index) m["Index"] = fmt.Sprintf("%v", n.Index)
m["RefCount"] = fmt.Sprintf("%v", n.RefCount)
m["RefIndex"] = fmt.Sprintf("%v", n.RefIndex)
gast.DumpHelper(n, source, level, m, nil) gast.DumpHelper(n, source, level, m, nil)
} }


@@ -30,36 +35,44 @@ func (n *FootnoteLink) Kind() gast.NodeKind {
// NewFootnoteLink returns a new FootnoteLink node. // NewFootnoteLink returns a new FootnoteLink node.
func NewFootnoteLink(index int) *FootnoteLink { func NewFootnoteLink(index int) *FootnoteLink {
return &FootnoteLink{ return &FootnoteLink{
Index: index,
Index: index,
RefCount: 0,
RefIndex: 0,
} }
} }


// A FootnoteBackLink struct represents a link to a footnote of Markdown
// A FootnoteBacklink struct represents a link to a footnote of Markdown
// (PHP Markdown Extra) text. // (PHP Markdown Extra) text.
type FootnoteBackLink struct {
type FootnoteBacklink struct {
gast.BaseInline gast.BaseInline
Index int
Index int
RefCount int
RefIndex int
} }


// Dump implements Node.Dump. // Dump implements Node.Dump.
func (n *FootnoteBackLink) Dump(source []byte, level int) {
func (n *FootnoteBacklink) Dump(source []byte, level int) {
m := map[string]string{} m := map[string]string{}
m["Index"] = fmt.Sprintf("%v", n.Index) m["Index"] = fmt.Sprintf("%v", n.Index)
m["RefCount"] = fmt.Sprintf("%v", n.RefCount)
m["RefIndex"] = fmt.Sprintf("%v", n.RefIndex)
gast.DumpHelper(n, source, level, m, nil) gast.DumpHelper(n, source, level, m, nil)
} }


// KindFootnoteBackLink is a NodeKind of the FootnoteBackLink node.
var KindFootnoteBackLink = gast.NewNodeKind("FootnoteBackLink")
// KindFootnoteBacklink is a NodeKind of the FootnoteBacklink node.
var KindFootnoteBacklink = gast.NewNodeKind("FootnoteBacklink")


// Kind implements Node.Kind. // Kind implements Node.Kind.
func (n *FootnoteBackLink) Kind() gast.NodeKind {
return KindFootnoteBackLink
func (n *FootnoteBacklink) Kind() gast.NodeKind {
return KindFootnoteBacklink
} }


// NewFootnoteBackLink returns a new FootnoteBackLink node.
func NewFootnoteBackLink(index int) *FootnoteBackLink {
return &FootnoteBackLink{
Index: index,
// NewFootnoteBacklink returns a new FootnoteBacklink node.
func NewFootnoteBacklink(index int) *FootnoteBacklink {
return &FootnoteBacklink{
Index: index,
RefCount: 0,
RefIndex: 0,
} }
} }




+ 1
- 1
vendor/github.com/yuin/goldmark/extension/definition_list.go View File

@@ -138,7 +138,7 @@ func (b *definitionDescriptionParser) Open(parent gast.Node, reader text.Reader,
para.Parent().RemoveChild(para.Parent(), para) para.Parent().RemoveChild(para.Parent(), para)
} }
cpos, padding := util.IndentPosition(line[pos+1:], pos+1, list.Offset-pos-1) cpos, padding := util.IndentPosition(line[pos+1:], pos+1, list.Offset-pos-1)
reader.AdvanceAndSetPadding(cpos, padding)
reader.AdvanceAndSetPadding(cpos+1, padding)


return ast.NewDefinitionDescription(), parser.HasChildren return ast.NewDefinitionDescription(), parser.HasChildren
} }


+ 386
- 35
vendor/github.com/yuin/goldmark/extension/footnote.go View File

@@ -2,6 +2,9 @@ package extension


import ( import (
"bytes" "bytes"
"fmt"
"strconv"

"github.com/yuin/goldmark" "github.com/yuin/goldmark"
gast "github.com/yuin/goldmark/ast" gast "github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension/ast" "github.com/yuin/goldmark/extension/ast"
@@ -10,10 +13,10 @@ import (
"github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text" "github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util" "github.com/yuin/goldmark/util"
"strconv"
) )


var footnoteListKey = parser.NewContextKey() var footnoteListKey = parser.NewContextKey()
var footnoteLinkListKey = parser.NewContextKey()


type footnoteBlockParser struct { type footnoteBlockParser struct {
} }
@@ -164,7 +167,20 @@ func (s *footnoteParser) Parse(parent gast.Node, block text.Reader, pc parser.Co
return nil return nil
} }


return ast.NewFootnoteLink(index)
fnlink := ast.NewFootnoteLink(index)
var fnlist []*ast.FootnoteLink
if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
fnlist = tmp.([]*ast.FootnoteLink)
} else {
fnlist = []*ast.FootnoteLink{}
pc.Set(footnoteLinkListKey, fnlist)
}
pc.Set(footnoteLinkListKey, append(fnlist, fnlink))
if line[0] == '!' {
parent.AppendChild(parent, gast.NewTextSegment(text.NewSegment(segment.Start, segment.Start+1)))
}

return fnlink
} }


type footnoteASTTransformer struct { type footnoteASTTransformer struct {
@@ -180,23 +196,62 @@ func NewFootnoteASTTransformer() parser.ASTTransformer {


func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) { func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) {
var list *ast.FootnoteList var list *ast.FootnoteList
if tlist := pc.Get(footnoteListKey); tlist != nil {
list = tlist.(*ast.FootnoteList)
} else {
return
var fnlist []*ast.FootnoteLink
if tmp := pc.Get(footnoteListKey); tmp != nil {
list = tmp.(*ast.FootnoteList)
}
if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
fnlist = tmp.([]*ast.FootnoteLink)
} }

pc.Set(footnoteListKey, nil) pc.Set(footnoteListKey, nil)
pc.Set(footnoteLinkListKey, nil)

if list == nil {
return
}

counter := map[int]int{}
if fnlist != nil {
for _, fnlink := range fnlist {
if fnlink.Index >= 0 {
counter[fnlink.Index]++
}
}
refCounter := map[int]int{}
for _, fnlink := range fnlist {
fnlink.RefCount = counter[fnlink.Index]
if _, ok := refCounter[fnlink.Index]; !ok {
refCounter[fnlink.Index] = 0
}
fnlink.RefIndex = refCounter[fnlink.Index]
refCounter[fnlink.Index]++
}
}
for footnote := list.FirstChild(); footnote != nil; { for footnote := list.FirstChild(); footnote != nil; {
var container gast.Node = footnote var container gast.Node = footnote
next := footnote.NextSibling() next := footnote.NextSibling()
if fc := container.LastChild(); fc != nil && gast.IsParagraph(fc) { if fc := container.LastChild(); fc != nil && gast.IsParagraph(fc) {
container = fc container = fc
} }
index := footnote.(*ast.Footnote).Index
fn := footnote.(*ast.Footnote)
index := fn.Index
if index < 0 { if index < 0 {
list.RemoveChild(list, footnote) list.RemoveChild(list, footnote)
} else { } else {
container.AppendChild(container, ast.NewFootnoteBackLink(index))
refCount := counter[index]
backLink := ast.NewFootnoteBacklink(index)
backLink.RefCount = refCount
backLink.RefIndex = 0
container.AppendChild(container, backLink)
if refCount > 1 {
for i := 1; i < refCount; i++ {
backLink := ast.NewFootnoteBacklink(index)
backLink.RefCount = refCount
backLink.RefIndex = i
container.AppendChild(container, backLink)
}
}
} }
footnote = next footnote = next
} }
@@ -214,19 +269,250 @@ func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Read
node.AppendChild(node, list) node.AppendChild(node, list)
} }


// FootnoteConfig holds configuration values for the footnote extension.
//
// Link* and Backlink* configurations have some variables:
// Occurrances of “^^” in the string will be replaced by the
// corresponding footnote number in the HTML output.
// Occurrances of “%%” will be replaced by a number for the
// reference (footnotes can have multiple references).
type FootnoteConfig struct {
html.Config

// IDPrefix is a prefix for the id attributes generated by footnotes.
IDPrefix []byte

// IDPrefix is a function that determines the id attribute for given Node.
IDPrefixFunction func(gast.Node) []byte

// LinkTitle is an optional title attribute for footnote links.
LinkTitle []byte

// BacklinkTitle is an optional title attribute for footnote backlinks.
BacklinkTitle []byte

// LinkClass is a class for footnote links.
LinkClass []byte

// BacklinkClass is a class for footnote backlinks.
BacklinkClass []byte

// BacklinkHTML is an HTML content for footnote backlinks.
BacklinkHTML []byte
}

// FootnoteOption interface is a functional option interface for the extension.
type FootnoteOption interface {
renderer.Option
// SetFootnoteOption sets given option to the extension.
SetFootnoteOption(*FootnoteConfig)
}

// NewFootnoteConfig returns a new Config with defaults.
func NewFootnoteConfig() FootnoteConfig {
return FootnoteConfig{
Config: html.NewConfig(),
LinkTitle: []byte(""),
BacklinkTitle: []byte(""),
LinkClass: []byte("footnote-ref"),
BacklinkClass: []byte("footnote-backref"),
BacklinkHTML: []byte("&#x21a9;&#xfe0e;"),
}
}

// SetOption implements renderer.SetOptioner.
func (c *FootnoteConfig) SetOption(name renderer.OptionName, value interface{}) {
switch name {
case optFootnoteIDPrefixFunction:
c.IDPrefixFunction = value.(func(gast.Node) []byte)
case optFootnoteIDPrefix:
c.IDPrefix = value.([]byte)
case optFootnoteLinkTitle:
c.LinkTitle = value.([]byte)
case optFootnoteBacklinkTitle:
c.BacklinkTitle = value.([]byte)
case optFootnoteLinkClass:
c.LinkClass = value.([]byte)
case optFootnoteBacklinkClass:
c.BacklinkClass = value.([]byte)
case optFootnoteBacklinkHTML:
c.BacklinkHTML = value.([]byte)
default:
c.Config.SetOption(name, value)
}
}

type withFootnoteHTMLOptions struct {
value []html.Option
}

func (o *withFootnoteHTMLOptions) SetConfig(c *renderer.Config) {
if o.value != nil {
for _, v := range o.value {
v.(renderer.Option).SetConfig(c)
}
}
}

func (o *withFootnoteHTMLOptions) SetFootnoteOption(c *FootnoteConfig) {
if o.value != nil {
for _, v := range o.value {
v.SetHTMLOption(&c.Config)
}
}
}

// WithFootnoteHTMLOptions is functional option that wraps goldmark HTMLRenderer options.
func WithFootnoteHTMLOptions(opts ...html.Option) FootnoteOption {
return &withFootnoteHTMLOptions{opts}
}

const optFootnoteIDPrefix renderer.OptionName = "FootnoteIDPrefix"

type withFootnoteIDPrefix struct {
value []byte
}

func (o *withFootnoteIDPrefix) SetConfig(c *renderer.Config) {
c.Options[optFootnoteIDPrefix] = o.value
}

func (o *withFootnoteIDPrefix) SetFootnoteOption(c *FootnoteConfig) {
c.IDPrefix = o.value
}

// WithFootnoteIDPrefix is a functional option that is a prefix for the id attributes generated by footnotes.
func WithFootnoteIDPrefix(a []byte) FootnoteOption {
return &withFootnoteIDPrefix{a}
}

const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction"

type withFootnoteIDPrefixFunction struct {
value func(gast.Node) []byte
}

func (o *withFootnoteIDPrefixFunction) SetConfig(c *renderer.Config) {
c.Options[optFootnoteIDPrefixFunction] = o.value
}

func (o *withFootnoteIDPrefixFunction) SetFootnoteOption(c *FootnoteConfig) {
c.IDPrefixFunction = o.value
}

// WithFootnoteIDPrefixFunction is a functional option that is a prefix for the id attributes generated by footnotes.
func WithFootnoteIDPrefixFunction(a func(gast.Node) []byte) FootnoteOption {
return &withFootnoteIDPrefixFunction{a}
}

const optFootnoteLinkTitle renderer.OptionName = "FootnoteLinkTitle"

type withFootnoteLinkTitle struct {
value []byte
}

func (o *withFootnoteLinkTitle) SetConfig(c *renderer.Config) {
c.Options[optFootnoteLinkTitle] = o.value
}

func (o *withFootnoteLinkTitle) SetFootnoteOption(c *FootnoteConfig) {
c.LinkTitle = o.value
}

// WithFootnoteLinkTitle is a functional option that is an optional title attribute for footnote links.
func WithFootnoteLinkTitle(a []byte) FootnoteOption {
return &withFootnoteLinkTitle{a}
}

const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle"

type withFootnoteBacklinkTitle struct {
value []byte
}

func (o *withFootnoteBacklinkTitle) SetConfig(c *renderer.Config) {
c.Options[optFootnoteBacklinkTitle] = o.value
}

func (o *withFootnoteBacklinkTitle) SetFootnoteOption(c *FootnoteConfig) {
c.BacklinkTitle = o.value
}

// WithFootnoteBacklinkTitle is a functional option that is an optional title attribute for footnote backlinks.
func WithFootnoteBacklinkTitle(a []byte) FootnoteOption {
return &withFootnoteBacklinkTitle{a}
}

const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass"

type withFootnoteLinkClass struct {
value []byte
}

func (o *withFootnoteLinkClass) SetConfig(c *renderer.Config) {
c.Options[optFootnoteLinkClass] = o.value
}

func (o *withFootnoteLinkClass) SetFootnoteOption(c *FootnoteConfig) {
c.LinkClass = o.value
}

// WithFootnoteLinkClass is a functional option that is a class for footnote links.
func WithFootnoteLinkClass(a []byte) FootnoteOption {
return &withFootnoteLinkClass{a}
}

const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass"

type withFootnoteBacklinkClass struct {
value []byte
}

func (o *withFootnoteBacklinkClass) SetConfig(c *renderer.Config) {
c.Options[optFootnoteBacklinkClass] = o.value
}

func (o *withFootnoteBacklinkClass) SetFootnoteOption(c *FootnoteConfig) {
c.BacklinkClass = o.value
}

// WithFootnoteBacklinkClass is a functional option that is a class for footnote backlinks.
func WithFootnoteBacklinkClass(a []byte) FootnoteOption {
return &withFootnoteBacklinkClass{a}
}

const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML"

type withFootnoteBacklinkHTML struct {
value []byte
}

func (o *withFootnoteBacklinkHTML) SetConfig(c *renderer.Config) {
c.Options[optFootnoteBacklinkHTML] = o.value
}

func (o *withFootnoteBacklinkHTML) SetFootnoteOption(c *FootnoteConfig) {
c.BacklinkHTML = o.value
}

// WithFootnoteBacklinkHTML is an HTML content for footnote backlinks.
func WithFootnoteBacklinkHTML(a []byte) FootnoteOption {
return &withFootnoteBacklinkHTML{a}
}

// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that // FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
// renders FootnoteLink nodes. // renders FootnoteLink nodes.
type FootnoteHTMLRenderer struct { type FootnoteHTMLRenderer struct {
html.Config
FootnoteConfig
} }


// NewFootnoteHTMLRenderer returns a new FootnoteHTMLRenderer. // NewFootnoteHTMLRenderer returns a new FootnoteHTMLRenderer.
func NewFootnoteHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
func NewFootnoteHTMLRenderer(opts ...FootnoteOption) renderer.NodeRenderer {
r := &FootnoteHTMLRenderer{ r := &FootnoteHTMLRenderer{
Config: html.NewConfig(),
FootnoteConfig: NewFootnoteConfig(),
} }
for _, opt := range opts { for _, opt := range opts {
opt.SetHTMLOption(&r.Config)
opt.SetFootnoteOption(&r.FootnoteConfig)
} }
return r return r
} }
@@ -234,7 +520,7 @@ func NewFootnoteHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs. // RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
func (r *FootnoteHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { func (r *FootnoteHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(ast.KindFootnoteLink, r.renderFootnoteLink) reg.Register(ast.KindFootnoteLink, r.renderFootnoteLink)
reg.Register(ast.KindFootnoteBackLink, r.renderFootnoteBackLink)
reg.Register(ast.KindFootnoteBacklink, r.renderFootnoteBacklink)
reg.Register(ast.KindFootnote, r.renderFootnote) reg.Register(ast.KindFootnote, r.renderFootnote)
reg.Register(ast.KindFootnoteList, r.renderFootnoteList) reg.Register(ast.KindFootnoteList, r.renderFootnoteList)
} }
@@ -243,25 +529,53 @@ func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byt
if entering { if entering {
n := node.(*ast.FootnoteLink) n := node.(*ast.FootnoteLink)
is := strconv.Itoa(n.Index) is := strconv.Itoa(n.Index)
_, _ = w.WriteString(`<sup id="fnref:`)
_, _ = w.WriteString(`<sup id="`)
_, _ = w.Write(r.idPrefix(node))
_, _ = w.WriteString(`fnref`)
if n.RefIndex > 0 {
_, _ = w.WriteString(fmt.Sprintf("%v", n.RefIndex))
}
_ = w.WriteByte(':')
_, _ = w.WriteString(is) _, _ = w.WriteString(is)
_, _ = w.WriteString(`"><a href="#fn:`)
_, _ = w.WriteString(`"><a href="#`)
_, _ = w.Write(r.idPrefix(node))
_, _ = w.WriteString(`fn:`)
_, _ = w.WriteString(is) _, _ = w.WriteString(is)
_, _ = w.WriteString(`" class="footnote-ref" role="doc-noteref">`)
_, _ = w.WriteString(`" class="`)
_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.LinkClass,
n.Index, n.RefCount))
if len(r.FootnoteConfig.LinkTitle) > 0 {
_, _ = w.WriteString(`" title="`)
_, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.LinkTitle, n.Index, n.RefCount)))
}
_, _ = w.WriteString(`" role="doc-noteref">`)

_, _ = w.WriteString(is) _, _ = w.WriteString(is)
_, _ = w.WriteString(`</a></sup>`) _, _ = w.WriteString(`</a></sup>`)
} }
return gast.WalkContinue, nil return gast.WalkContinue, nil
} }


func (r *FootnoteHTMLRenderer) renderFootnoteBackLink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
func (r *FootnoteHTMLRenderer) renderFootnoteBacklink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
if entering { if entering {
n := node.(*ast.FootnoteBackLink)
n := node.(*ast.FootnoteBacklink)
is := strconv.Itoa(n.Index) is := strconv.Itoa(n.Index)
_, _ = w.WriteString(` <a href="#fnref:`)
_, _ = w.WriteString(`&#160;<a href="#`)
_, _ = w.Write(r.idPrefix(node))
_, _ = w.WriteString(`fnref`)
if n.RefIndex > 0 {
_, _ = w.WriteString(fmt.Sprintf("%v", n.RefIndex))
}
_ = w.WriteByte(':')
_, _ = w.WriteString(is) _, _ = w.WriteString(is)
_, _ = w.WriteString(`" class="footnote-backref" role="doc-backlink">`)
_, _ = w.WriteString("&#x21a9;&#xfe0e;")
_, _ = w.WriteString(`" class="`)
_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkClass, n.Index, n.RefCount))
if len(r.FootnoteConfig.BacklinkTitle) > 0 {
_, _ = w.WriteString(`" title="`)
_, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.BacklinkTitle, n.Index, n.RefCount)))
}
_, _ = w.WriteString(`" role="doc-backlink">`)
_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkHTML, n.Index, n.RefCount))
_, _ = w.WriteString(`</a>`) _, _ = w.WriteString(`</a>`)
} }
return gast.WalkContinue, nil return gast.WalkContinue, nil
@@ -271,9 +585,11 @@ func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, n
n := node.(*ast.Footnote) n := node.(*ast.Footnote)
is := strconv.Itoa(n.Index) is := strconv.Itoa(n.Index)
if entering { if entering {
_, _ = w.WriteString(`<li id="fn:`)
_, _ = w.WriteString(`<li id="`)
_, _ = w.Write(r.idPrefix(node))
_, _ = w.WriteString(`fn:`)
_, _ = w.WriteString(is) _, _ = w.WriteString(is)
_, _ = w.WriteString(`" role="doc-endnote"`)
_, _ = w.WriteString(`"`)
if node.Attributes() != nil { if node.Attributes() != nil {
html.RenderAttributes(w, node, html.ListItemAttributeFilter) html.RenderAttributes(w, node, html.ListItemAttributeFilter)
} }
@@ -285,14 +601,8 @@ func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, n
} }


func (r *FootnoteHTMLRenderer) renderFootnoteList(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) { func (r *FootnoteHTMLRenderer) renderFootnoteList(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
tag := "section"
if r.Config.XHTML {
tag = "div"
}
if entering { if entering {
_, _ = w.WriteString("<")
_, _ = w.WriteString(tag)
_, _ = w.WriteString(` class="footnotes" role="doc-endnotes"`)
_, _ = w.WriteString(`<div class="footnotes" role="doc-endnotes"`)
if node.Attributes() != nil { if node.Attributes() != nil {
html.RenderAttributes(w, node, html.GlobalAttributeFilter) html.RenderAttributes(w, node, html.GlobalAttributeFilter)
} }
@@ -305,18 +615,59 @@ func (r *FootnoteHTMLRenderer) renderFootnoteList(w util.BufWriter, source []byt
_, _ = w.WriteString("<ol>\n") _, _ = w.WriteString("<ol>\n")
} else { } else {
_, _ = w.WriteString("</ol>\n") _, _ = w.WriteString("</ol>\n")
_, _ = w.WriteString("</")
_, _ = w.WriteString(tag)
_, _ = w.WriteString(">\n")
_, _ = w.WriteString("</div>\n")
} }
return gast.WalkContinue, nil return gast.WalkContinue, nil
} }


func (r *FootnoteHTMLRenderer) idPrefix(node gast.Node) []byte {
if r.FootnoteConfig.IDPrefix != nil {
return r.FootnoteConfig.IDPrefix
}
if r.FootnoteConfig.IDPrefixFunction != nil {
return r.FootnoteConfig.IDPrefixFunction(node)
}
return []byte("")
}

func applyFootnoteTemplate(b []byte, index, refCount int) []byte {
fast := true
for i, c := range b {
if i != 0 {
if b[i-1] == '^' && c == '^' {
fast = false
break
}
if b[i-1] == '%' && c == '%' {
fast = false
break
}
}
}
if fast {
return b
}
is := []byte(strconv.Itoa(index))
rs := []byte(strconv.Itoa(refCount))
ret := bytes.Replace(b, []byte("^^"), is, -1)
return bytes.Replace(ret, []byte("%%"), rs, -1)
}

type footnote struct { type footnote struct {
options []FootnoteOption
} }


// Footnote is an extension that allow you to use PHP Markdown Extra Footnotes. // Footnote is an extension that allow you to use PHP Markdown Extra Footnotes.
var Footnote = &footnote{}
var Footnote = &footnote{
options: []FootnoteOption{},
}

// NewFootnote returns a new extension with given options.
func NewFootnote(opts ...FootnoteOption) goldmark.Extender {
return &footnote{
options: opts,
}
}


func (e *footnote) Extend(m goldmark.Markdown) { func (e *footnote) Extend(m goldmark.Markdown) {
m.Parser().AddOptions( m.Parser().AddOptions(
@@ -331,6 +682,6 @@ func (e *footnote) Extend(m goldmark.Markdown) {
), ),
) )
m.Renderer().AddOptions(renderer.WithNodeRenderers( m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(NewFootnoteHTMLRenderer(), 500),
util.Prioritized(NewFootnoteHTMLRenderer(e.options...), 500),
)) ))
} }

+ 27
- 12
vendor/github.com/yuin/goldmark/extension/linkify.go View File

@@ -11,9 +11,9 @@ import (
"github.com/yuin/goldmark/util" "github.com/yuin/goldmark/util"
) )


var wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]+(?:(?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
var wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]+(?:[/#?][-a-zA-Z0-9@:%_\+.~#!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`)


var urlRegexp = regexp.MustCompile(`^(?:http|https|ftp):\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]+(?:(?:/|[#?])[-a-zA-Z0-9@:%_+.~#$!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
var urlRegexp = regexp.MustCompile(`^(?:http|https|ftp)://[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]+(?::\d+)?(?:[/#?][-a-zA-Z0-9@:%_+.~#$!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`)


// An LinkifyConfig struct is a data structure that holds configuration of the // An LinkifyConfig struct is a data structure that holds configuration of the
// Linkify extension. // Linkify extension.
@@ -24,10 +24,12 @@ type LinkifyConfig struct {
EmailRegexp *regexp.Regexp EmailRegexp *regexp.Regexp
} }


const optLinkifyAllowedProtocols parser.OptionName = "LinkifyAllowedProtocols"
const optLinkifyURLRegexp parser.OptionName = "LinkifyURLRegexp"
const optLinkifyWWWRegexp parser.OptionName = "LinkifyWWWRegexp"
const optLinkifyEmailRegexp parser.OptionName = "LinkifyEmailRegexp"
const (
optLinkifyAllowedProtocols parser.OptionName = "LinkifyAllowedProtocols"
optLinkifyURLRegexp parser.OptionName = "LinkifyURLRegexp"
optLinkifyWWWRegexp parser.OptionName = "LinkifyWWWRegexp"
optLinkifyEmailRegexp parser.OptionName = "LinkifyEmailRegexp"
)


// SetOption implements SetOptioner. // SetOption implements SetOptioner.
func (c *LinkifyConfig) SetOption(name parser.OptionName, value interface{}) { func (c *LinkifyConfig) SetOption(name parser.OptionName, value interface{}) {
@@ -156,10 +158,12 @@ func (s *linkifyParser) Trigger() []byte {
return []byte{' ', '*', '_', '~', '('} return []byte{' ', '*', '_', '~', '('}
} }


var protoHTTP = []byte("http:")
var protoHTTPS = []byte("https:")
var protoFTP = []byte("ftp:")
var domainWWW = []byte("www.")
var (
protoHTTP = []byte("http:")
protoHTTPS = []byte("https:")
protoFTP = []byte("ftp:")
domainWWW = []byte("www.")
)


func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node { func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
if pc.IsInLinkLabel() { if pc.IsInLinkLabel() {
@@ -269,9 +273,20 @@ func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Cont
s := segment.WithStop(segment.Start + 1) s := segment.WithStop(segment.Start + 1)
ast.MergeOrAppendTextSegment(parent, s) ast.MergeOrAppendTextSegment(parent, s)
} }
consumes += m[1]
i := m[1] - 1
for ; i > 0; i-- {
c := line[i]
switch c {
case '?', '!', '.', ',', ':', '*', '_', '~':
default:
goto endfor
}
}
endfor:
i++
consumes += i
block.Advance(consumes) block.Advance(consumes)
n := ast.NewTextSegment(text.NewSegment(start, start+m[1]))
n := ast.NewTextSegment(text.NewSegment(start, start+i))
link := ast.NewAutoLink(typ, n) link := ast.NewAutoLink(typ, n)
link.Protocol = protocol link.Protocol = protocol
return link return link


+ 273
- 40
vendor/github.com/yuin/goldmark/extension/table.go View File

@@ -15,7 +15,121 @@ import (
"github.com/yuin/goldmark/util" "github.com/yuin/goldmark/util"
) )


var tableDelimRegexp = regexp.MustCompile(`^[\s\-\|\:]+$`)
var escapedPipeCellListKey = parser.NewContextKey()

type escapedPipeCell struct {
Cell *ast.TableCell
Pos []int
Transformed bool
}

// TableCellAlignMethod indicates how are table cells aligned in HTML format.indicates how are table cells aligned in HTML format.
type TableCellAlignMethod int

const (
// TableCellAlignDefault renders alignments by default method.
// With XHTML, alignments are rendered as an align attribute.
// With HTML5, alignments are rendered as a style attribute.
TableCellAlignDefault TableCellAlignMethod = iota

// TableCellAlignAttribute renders alignments as an align attribute.
TableCellAlignAttribute

// TableCellAlignStyle renders alignments as a style attribute.
TableCellAlignStyle

// TableCellAlignNone does not care about alignments.
// If you using classes or other styles, you can add these attributes
// in an ASTTransformer.
TableCellAlignNone
)

// TableConfig struct holds options for the extension.
type TableConfig struct {
html.Config

// TableCellAlignMethod indicates how are table celss aligned.
TableCellAlignMethod TableCellAlignMethod
}

// TableOption interface is a functional option interface for the extension.
type TableOption interface {
renderer.Option
// SetTableOption sets given option to the extension.
SetTableOption(*TableConfig)
}

// NewTableConfig returns a new Config with defaults.
func NewTableConfig() TableConfig {
return TableConfig{
Config: html.NewConfig(),
TableCellAlignMethod: TableCellAlignDefault,
}
}

// SetOption implements renderer.SetOptioner.
func (c *TableConfig) SetOption(name renderer.OptionName, value interface{}) {
switch name {
case optTableCellAlignMethod:
c.TableCellAlignMethod = value.(TableCellAlignMethod)
default:
c.Config.SetOption(name, value)
}
}

type withTableHTMLOptions struct {
value []html.Option
}

func (o *withTableHTMLOptions) SetConfig(c *renderer.Config) {
if o.value != nil {
for _, v := range o.value {
v.(renderer.Option).SetConfig(c)
}
}
}

func (o *withTableHTMLOptions) SetTableOption(c *TableConfig) {
if o.value != nil {
for _, v := range o.value {
v.SetHTMLOption(&c.Config)
}
}
}

// WithTableHTMLOptions is functional option that wraps goldmark HTMLRenderer options.
func WithTableHTMLOptions(opts ...html.Option) TableOption {
return &withTableHTMLOptions{opts}
}

const optTableCellAlignMethod renderer.OptionName = "TableTableCellAlignMethod"

type withTableCellAlignMethod struct {
value TableCellAlignMethod
}

func (o *withTableCellAlignMethod) SetConfig(c *renderer.Config) {
c.Options[optTableCellAlignMethod] = o.value
}

func (o *withTableCellAlignMethod) SetTableOption(c *TableConfig) {
c.TableCellAlignMethod = o.value
}

// WithTableCellAlignMethod is a functional option that indicates how are table cells aligned in HTML format.
func WithTableCellAlignMethod(a TableCellAlignMethod) TableOption {
return &withTableCellAlignMethod{a}
}

func isTableDelim(bs []byte) bool {
for _, b := range bs {
if !(util.IsSpace(b) || b == '-' || b == '|' || b == ':') {
return false
}
}
return true
}

var tableDelimLeft = regexp.MustCompile(`^\s*\:\-+\s*$`) var tableDelimLeft = regexp.MustCompile(`^\s*\:\-+\s*$`)
var tableDelimRight = regexp.MustCompile(`^\s*\-+\:\s*$`) var tableDelimRight = regexp.MustCompile(`^\s*\-+\:\s*$`)
var tableDelimCenter = regexp.MustCompile(`^\s*\:\-+\:\s*$`) var tableDelimCenter = regexp.MustCompile(`^\s*\:\-+\:\s*$`)
@@ -37,25 +151,34 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text.
if lines.Len() < 2 { if lines.Len() < 2 {
return return
} }
alignments := b.parseDelimiter(lines.At(1), reader)
if alignments == nil {
return
}
header := b.parseRow(lines.At(0), alignments, true, reader)
if header == nil || len(alignments) != header.ChildCount() {
return
}
table := ast.NewTable()
table.Alignments = alignments
table.AppendChild(table, ast.NewTableHeader(header))
for i := 2; i < lines.Len(); i++ {
table.AppendChild(table, b.parseRow(lines.At(i), alignments, false, reader))
for i := 1; i < lines.Len(); i++ {
alignments := b.parseDelimiter(lines.At(i), reader)
if alignments == nil {
continue
}
header := b.parseRow(lines.At(i-1), alignments, true, reader, pc)
if header == nil || len(alignments) != header.ChildCount() {
return
}
table := ast.NewTable()
table.Alignments = alignments
table.AppendChild(table, ast.NewTableHeader(header))
for j := i + 1; j < lines.Len(); j++ {
table.AppendChild(table, b.parseRow(lines.At(j), alignments, false, reader, pc))
}
node.Lines().SetSliced(0, i-1)
node.Parent().InsertAfter(node.Parent(), node, table)
if node.Lines().Len() == 0 {
node.Parent().RemoveChild(node.Parent(), node)
} else {
last := node.Lines().At(i - 2)
last.Stop = last.Stop - 1 // trim last newline(\n)
node.Lines().Set(i-2, last)
}
} }
node.Parent().InsertBefore(node.Parent(), node, table)
node.Parent().RemoveChild(node.Parent(), node)
} }


func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, isHeader bool, reader text.Reader) *ast.TableRow {
func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, isHeader bool, reader text.Reader, pc parser.Context) *ast.TableRow {
source := reader.Source() source := reader.Source()
line := segment.Value(source) line := segment.Value(source)
pos := 0 pos := 0
@@ -79,18 +202,39 @@ func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []
} else { } else {
alignment = alignments[i] alignment = alignments[i]
} }
closure := util.FindClosure(line[pos:], byte(0), '|', true, false)
if closure < 0 {
closure = len(line[pos:])
}

var escapedCell *escapedPipeCell
node := ast.NewTableCell() node := ast.NewTableCell()
seg := text.NewSegment(segment.Start+pos, segment.Start+pos+closure)
node.Alignment = alignment
hasBacktick := false
closure := pos
for ; closure < limit; closure++ {
if line[closure] == '`' {
hasBacktick = true
}
if line[closure] == '|' {
if closure == 0 || line[closure-1] != '\\' {
break
} else if hasBacktick {
if escapedCell == nil {
escapedCell = &escapedPipeCell{node, []int{}, false}
escapedList := pc.ComputeIfAbsent(escapedPipeCellListKey,
func() interface{} {
return []*escapedPipeCell{}
}).([]*escapedPipeCell)
escapedList = append(escapedList, escapedCell)
pc.Set(escapedPipeCellListKey, escapedList)
}
escapedCell.Pos = append(escapedCell.Pos, segment.Start+closure-1)
}
}
}
seg := text.NewSegment(segment.Start+pos, segment.Start+closure)
seg = seg.TrimLeftSpace(source) seg = seg.TrimLeftSpace(source)
seg = seg.TrimRightSpace(source) seg = seg.TrimRightSpace(source)
node.Lines().Append(seg) node.Lines().Append(seg)
node.Alignment = alignment
row.AppendChild(row, node) row.AppendChild(row, node)
pos += closure + 1
pos = closure + 1
} }
for ; i < len(alignments); i++ { for ; i < len(alignments); i++ {
row.AppendChild(row, ast.NewTableCell()) row.AppendChild(row, ast.NewTableCell())
@@ -100,7 +244,7 @@ func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []


func (b *tableParagraphTransformer) parseDelimiter(segment text.Segment, reader text.Reader) []ast.Alignment { func (b *tableParagraphTransformer) parseDelimiter(segment text.Segment, reader text.Reader) []ast.Alignment {
line := segment.Value(reader.Source()) line := segment.Value(reader.Source())
if !tableDelimRegexp.Match(line) {
if !isTableDelim(line) {
return nil return nil
} }
cols := bytes.Split(line, []byte{'|'}) cols := bytes.Split(line, []byte{'|'})
@@ -128,19 +272,74 @@ func (b *tableParagraphTransformer) parseDelimiter(segment text.Segment, reader
return alignments return alignments
} }


type tableASTTransformer struct {
}

var defaultTableASTTransformer = &tableASTTransformer{}

// NewTableASTTransformer returns a parser.ASTTransformer for tables.
func NewTableASTTransformer() parser.ASTTransformer {
return defaultTableASTTransformer
}

func (a *tableASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) {
lst := pc.Get(escapedPipeCellListKey)
if lst == nil {
return
}
pc.Set(escapedPipeCellListKey, nil)
for _, v := range lst.([]*escapedPipeCell) {
if v.Transformed {
continue
}
_ = gast.Walk(v.Cell, func(n gast.Node, entering bool) (gast.WalkStatus, error) {
if !entering || n.Kind() != gast.KindCodeSpan {
return gast.WalkContinue, nil
}

for c := n.FirstChild(); c != nil; {
next := c.NextSibling()
if c.Kind() != gast.KindText {
c = next
continue
}
parent := c.Parent()
ts := &c.(*gast.Text).Segment
n := c
for _, v := range lst.([]*escapedPipeCell) {
for _, pos := range v.Pos {
if ts.Start <= pos && pos < ts.Stop {
segment := n.(*gast.Text).Segment
n1 := gast.NewRawTextSegment(segment.WithStop(pos))
n2 := gast.NewRawTextSegment(segment.WithStart(pos + 1))
parent.InsertAfter(parent, n, n1)
parent.InsertAfter(parent, n1, n2)
parent.RemoveChild(parent, n)
n = n2
v.Transformed = true
}
}
}
c = next
}
return gast.WalkContinue, nil
})
}
}

// TableHTMLRenderer is a renderer.NodeRenderer implementation that // TableHTMLRenderer is a renderer.NodeRenderer implementation that
// renders Table nodes. // renders Table nodes.
type TableHTMLRenderer struct { type TableHTMLRenderer struct {
html.Config
TableConfig
} }


// NewTableHTMLRenderer returns a new TableHTMLRenderer. // NewTableHTMLRenderer returns a new TableHTMLRenderer.
func NewTableHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
func NewTableHTMLRenderer(opts ...TableOption) renderer.NodeRenderer {
r := &TableHTMLRenderer{ r := &TableHTMLRenderer{
Config: html.NewConfig(),
TableConfig: NewTableConfig(),
} }
for _, opt := range opts { for _, opt := range opts {
opt.SetHTMLOption(&r.Config)
opt.SetTableOption(&r.TableConfig)
} }
return r return r
} }
@@ -281,14 +480,33 @@ func (r *TableHTMLRenderer) renderTableCell(w util.BufWriter, source []byte, nod
tag = "th" tag = "th"
} }
if entering { if entering {
align := ""
fmt.Fprintf(w, "<%s", tag)
if n.Alignment != ast.AlignNone { if n.Alignment != ast.AlignNone {
if _, ok := n.AttributeString("align"); !ok { // Skip align render if overridden
// TODO: "align" is deprecated. style="text-align:%s" instead?
align = fmt.Sprintf(` align="%s"`, n.Alignment.String())
amethod := r.TableConfig.TableCellAlignMethod
if amethod == TableCellAlignDefault {
if r.Config.XHTML {
amethod = TableCellAlignAttribute
} else {
amethod = TableCellAlignStyle
}
}
switch amethod {
case TableCellAlignAttribute:
if _, ok := n.AttributeString("align"); !ok { // Skip align render if overridden
fmt.Fprintf(w, ` align="%s"`, n.Alignment.String())
}
case TableCellAlignStyle:
v, ok := n.AttributeString("style")
var cob util.CopyOnWriteBuffer
if ok {
cob = util.NewCopyOnWriteBuffer(v.([]byte))
cob.AppendByte(';')
}
style := fmt.Sprintf("text-align:%s", n.Alignment.String())
cob.AppendString(style)
n.SetAttributeString("style", cob.Bytes())
} }
} }
fmt.Fprintf(w, "<%s", tag)
if n.Attributes() != nil { if n.Attributes() != nil {
if tag == "td" { if tag == "td" {
html.RenderAttributes(w, n, TableTdCellAttributeFilter) // <td> html.RenderAttributes(w, n, TableTdCellAttributeFilter) // <td>
@@ -296,7 +514,7 @@ func (r *TableHTMLRenderer) renderTableCell(w util.BufWriter, source []byte, nod
html.RenderAttributes(w, n, TableThCellAttributeFilter) // <th> html.RenderAttributes(w, n, TableThCellAttributeFilter) // <th>
} }
} }
fmt.Fprintf(w, "%s>", align)
_ = w.WriteByte('>')
} else { } else {
fmt.Fprintf(w, "</%s>\n", tag) fmt.Fprintf(w, "</%s>\n", tag)
} }
@@ -304,16 +522,31 @@ func (r *TableHTMLRenderer) renderTableCell(w util.BufWriter, source []byte, nod
} }


type table struct { type table struct {
options []TableOption
} }


// Table is an extension that allow you to use GFM tables . // Table is an extension that allow you to use GFM tables .
var Table = &table{}
var Table = &table{
options: []TableOption{},
}

// NewTable returns a new extension with given options.
func NewTable(opts ...TableOption) goldmark.Extender {
return &table{
options: opts,
}
}


func (e *table) Extend(m goldmark.Markdown) { func (e *table) Extend(m goldmark.Markdown) {
m.Parser().AddOptions(parser.WithParagraphTransformers(
util.Prioritized(NewTableParagraphTransformer(), 200),
))
m.Parser().AddOptions(
parser.WithParagraphTransformers(
util.Prioritized(NewTableParagraphTransformer(), 200),
),
parser.WithASTTransformers(
util.Prioritized(defaultTableASTTransformer, 0),
),
)
m.Renderer().AddOptions(renderer.WithNodeRenderers( m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(NewTableHTMLRenderer(), 500),
util.Prioritized(NewTableHTMLRenderer(e.options...), 500),
)) ))
} }

+ 81
- 15
vendor/github.com/yuin/goldmark/extension/typographer.go View File

@@ -10,6 +10,27 @@ import (
"github.com/yuin/goldmark/util" "github.com/yuin/goldmark/util"
) )


var uncloseCounterKey = parser.NewContextKey()

type unclosedCounter struct {
Single int
Double int
}

func (u *unclosedCounter) Reset() {
u.Single = 0
u.Double = 0
}

func getUnclosedCounter(pc parser.Context) *unclosedCounter {
v := pc.Get(uncloseCounterKey)
if v == nil {
v = &unclosedCounter{}
pc.Set(uncloseCounterKey, v)
}
return v.(*unclosedCounter)
}

// TypographicPunctuation is a key of the punctuations that can be replaced with // TypographicPunctuation is a key of the punctuations that can be replaced with
// typographic entities. // typographic entities.
type TypographicPunctuation int type TypographicPunctuation int
@@ -139,11 +160,10 @@ func NewTypographerParser(opts ...TypographerOption) parser.InlineParser {
} }


func (s *typographerParser) Trigger() []byte { func (s *typographerParser) Trigger() []byte {
return []byte{'\'', '"', '-', '.', '<', '>'}
return []byte{'\'', '"', '-', '.', ',', '<', '>', '*', '['}
} }


func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node { func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
before := block.PrecendingCharacter()
line, _ := block.PeekLine() line, _ := block.PeekLine()
c := line[0] c := line[0]
if len(line) > 2 { if len(line) > 2 {
@@ -189,10 +209,12 @@ func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser
} }
} }
if c == '\'' || c == '"' { if c == '\'' || c == '"' {
before := block.PrecendingCharacter()
d := parser.ScanDelimiter(line, before, 1, defaultTypographerDelimiterProcessor) d := parser.ScanDelimiter(line, before, 1, defaultTypographerDelimiterProcessor)
if d == nil { if d == nil {
return nil return nil
} }
counter := getUnclosedCounter(pc)
if c == '\'' { if c == '\'' {
if s.Substitutions[Apostrophe] != nil { if s.Substitutions[Apostrophe] != nil {
// Handle decade abbrevations such as '90s // Handle decade abbrevations such as '90s
@@ -201,13 +223,20 @@ func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser
if len(line) > 4 { if len(line) > 4 {
after = util.ToRune(line, 4) after = util.ToRune(line, 4)
} }
if len(line) == 3 || unicode.IsSpace(after) || unicode.IsPunct(after) {
if len(line) == 3 || util.IsSpaceRune(after) || util.IsPunctRune(after) {
node := gast.NewString(s.Substitutions[Apostrophe]) node := gast.NewString(s.Substitutions[Apostrophe])
node.SetCode(true) node.SetCode(true)
block.Advance(1) block.Advance(1)
return node return node
} }
} }
// special cases: 'twas, 'em, 'net
if len(line) > 1 && (unicode.IsPunct(before) || unicode.IsSpace(before)) && (line[1] == 't' || line[1] == 'e' || line[1] == 'n' || line[1] == 'l') {
node := gast.NewString(s.Substitutions[Apostrophe])
node.SetCode(true)
block.Advance(1)
return node
}
// Convert normal apostrophes. This is probably more flexible than necessary but // Convert normal apostrophes. This is probably more flexible than necessary but
// converts any apostrophe in between two alphanumerics. // converts any apostrophe in between two alphanumerics.
if len(line) > 1 && (unicode.IsDigit(before) || unicode.IsLetter(before)) && (unicode.IsLetter(util.ToRune(line, 1))) { if len(line) > 1 && (unicode.IsDigit(before) || unicode.IsLetter(before)) && (unicode.IsLetter(util.ToRune(line, 1))) {
@@ -218,16 +247,43 @@ func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser
} }
} }
if s.Substitutions[LeftSingleQuote] != nil && d.CanOpen && !d.CanClose { if s.Substitutions[LeftSingleQuote] != nil && d.CanOpen && !d.CanClose {
node := gast.NewString(s.Substitutions[LeftSingleQuote])
nt := LeftSingleQuote
// special cases: Alice's, I'm, Don't, You'd
if len(line) > 1 && (line[1] == 's' || line[1] == 'm' || line[1] == 't' || line[1] == 'd') && (len(line) < 3 || util.IsPunct(line[2]) || util.IsSpace(line[2])) {
nt = RightSingleQuote
}
// special cases: I've, I'll, You're
if len(line) > 2 && ((line[1] == 'v' && line[2] == 'e') || (line[1] == 'l' && line[2] == 'l') || (line[1] == 'r' && line[2] == 'e')) && (len(line) < 4 || util.IsPunct(line[3]) || util.IsSpace(line[3])) {
nt = RightSingleQuote
}
if nt == LeftSingleQuote {
counter.Single++
}

node := gast.NewString(s.Substitutions[nt])
node.SetCode(true) node.SetCode(true)
block.Advance(1) block.Advance(1)
return node return node
} }
if s.Substitutions[RightSingleQuote] != nil && d.CanClose && !d.CanOpen {
node := gast.NewString(s.Substitutions[RightSingleQuote])
node.SetCode(true)
block.Advance(1)
return node
if s.Substitutions[RightSingleQuote] != nil {
// plural possesives and abbreviations: Smiths', doin'
if len(line) > 1 && unicode.IsSpace(util.ToRune(line, 0)) || unicode.IsPunct(util.ToRune(line, 0)) && (len(line) > 2 && !unicode.IsDigit(util.ToRune(line, 1))) {
node := gast.NewString(s.Substitutions[RightSingleQuote])
node.SetCode(true)
block.Advance(1)
return node
}
}
if s.Substitutions[RightSingleQuote] != nil && counter.Single > 0 {
isClose := d.CanClose && !d.CanOpen
maybeClose := d.CanClose && d.CanOpen && len(line) > 1 && unicode.IsPunct(util.ToRune(line, 1)) && (len(line) == 2 || (len(line) > 2 && util.IsPunct(line[2]) || util.IsSpace(line[2])))
if isClose || maybeClose {
node := gast.NewString(s.Substitutions[RightSingleQuote])
node.SetCode(true)
block.Advance(1)
counter.Single--
return node
}
} }
} }
if c == '"' { if c == '"' {
@@ -235,13 +291,23 @@ func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser
node := gast.NewString(s.Substitutions[LeftDoubleQuote]) node := gast.NewString(s.Substitutions[LeftDoubleQuote])
node.SetCode(true) node.SetCode(true)
block.Advance(1) block.Advance(1)
counter.Double++
return node return node
} }
if s.Substitutions[RightDoubleQuote] != nil && d.CanClose && !d.CanOpen {
node := gast.NewString(s.Substitutions[RightDoubleQuote])
node.SetCode(true)
block.Advance(1)
return node
if s.Substitutions[RightDoubleQuote] != nil && counter.Double > 0 {
isClose := d.CanClose && !d.CanOpen
maybeClose := d.CanClose && d.CanOpen && len(line) > 1 && (unicode.IsPunct(util.ToRune(line, 1))) && (len(line) == 2 || (len(line) > 2 && util.IsPunct(line[2]) || util.IsSpace(line[2])))
if isClose || maybeClose {
// special case: "Monitor 21""
if len(line) > 1 && line[1] == '"' && unicode.IsDigit(before) {
return nil
}
node := gast.NewString(s.Substitutions[RightDoubleQuote])
node.SetCode(true)
block.Advance(1)
counter.Double--
return node
}
} }
} }
} }
@@ -249,7 +315,7 @@ func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser
} }


func (s *typographerParser) CloseBlock(parent gast.Node, pc parser.Context) { func (s *typographerParser) CloseBlock(parent gast.Node, pc parser.Context) {
// nothing to do
getUnclosedCounter(pc).Reset()
} }


type typographer struct { type typographer struct {


+ 1
- 1
vendor/github.com/yuin/goldmark/go.mod View File

@@ -1,3 +1,3 @@
module github.com/yuin/goldmark module github.com/yuin/goldmark


go 1.13
go 1.18

+ 10
- 1
vendor/github.com/yuin/goldmark/parser/attribute.go View File

@@ -89,7 +89,11 @@ func parseAttribute(reader text.Reader) (Attribute, bool) {
reader.Advance(1) reader.Advance(1)
line, _ := reader.PeekLine() line, _ := reader.PeekLine()
i := 0 i := 0
for ; i < len(line) && !util.IsSpace(line[i]) && (!util.IsPunct(line[i]) || line[i] == '_' || line[i] == '-'); i++ {
// HTML5 allows any kind of characters as id, but XHTML restricts characters for id.
// CommonMark is basically defined for XHTML(even though it is legacy).
// So we restrict id characters.
for ; i < len(line) && !util.IsSpace(line[i]) &&
(!util.IsPunct(line[i]) || line[i] == '_' || line[i] == '-' || line[i] == ':' || line[i] == '.'); i++ {
} }
name := attrNameClass name := attrNameClass
if c == '#' { if c == '#' {
@@ -129,6 +133,11 @@ func parseAttribute(reader text.Reader) (Attribute, bool) {
if !ok { if !ok {
return Attribute{}, false return Attribute{}, false
} }
if bytes.Equal(name, attrNameClass) {
if _, ok = value.([]byte); !ok {
return Attribute{}, false
}
}
return Attribute{Name: name, Value: value}, true return Attribute{Name: name, Value: value}, true
} }




+ 5
- 1
vendor/github.com/yuin/goldmark/parser/atx_heading.go View File

@@ -91,6 +91,9 @@ func (b *atxHeadingParser) Open(parent ast.Node, reader text.Reader, pc Context)
if i == pos || level > 6 { if i == pos || level > 6 {
return nil, NoChildren return nil, NoChildren
} }
if i == len(line) { // alone '#' (without a new line character)
return ast.NewHeading(level), NoChildren
}
l := util.TrimLeftSpaceLength(line[i:]) l := util.TrimLeftSpaceLength(line[i:])
if l == 0 { if l == 0 {
return nil, NoChildren return nil, NoChildren
@@ -126,7 +129,8 @@ func (b *atxHeadingParser) Open(parent ast.Node, reader text.Reader, pc Context)
if closureClose > 0 { if closureClose > 0 {
reader.Advance(closureClose) reader.Advance(closureClose)
attrs, ok := ParseAttributes(reader) attrs, ok := ParseAttributes(reader)
parsed = ok
rest, _ := reader.PeekLine()
parsed = ok && util.IsBlank(rest)
if parsed { if parsed {
for _, attr := range attrs { for _, attr := range attrs {
node.SetAttribute(attr.Name, attr.Value) node.SetAttribute(attr.Name, attr.Value)


+ 21
- 0
vendor/github.com/yuin/goldmark/parser/code_block.go View File

@@ -31,6 +31,10 @@ func (b *codeBlockParser) Open(parent ast.Node, reader text.Reader, pc Context)
node := ast.NewCodeBlock() node := ast.NewCodeBlock()
reader.AdvanceAndSetPadding(pos, padding) reader.AdvanceAndSetPadding(pos, padding)
_, segment = reader.PeekLine() _, segment = reader.PeekLine()
// if code block line starts with a tab, keep a tab as it is.
if segment.Padding != 0 {
preserveLeadingTabInCodeBlock(&segment, reader, 0)
}
node.Lines().Append(segment) node.Lines().Append(segment)
reader.Advance(segment.Len() - 1) reader.Advance(segment.Len() - 1)
return node, NoChildren return node, NoChildren
@@ -49,6 +53,12 @@ func (b *codeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context
} }
reader.AdvanceAndSetPadding(pos, padding) reader.AdvanceAndSetPadding(pos, padding)
_, segment = reader.PeekLine() _, segment = reader.PeekLine()

// if code block line starts with a tab, keep a tab as it is.
if segment.Padding != 0 {
preserveLeadingTabInCodeBlock(&segment, reader, 0)
}

node.Lines().Append(segment) node.Lines().Append(segment)
reader.Advance(segment.Len() - 1) reader.Advance(segment.Len() - 1)
return Continue | NoChildren return Continue | NoChildren
@@ -77,3 +87,14 @@ func (b *codeBlockParser) CanInterruptParagraph() bool {
func (b *codeBlockParser) CanAcceptIndentedLine() bool { func (b *codeBlockParser) CanAcceptIndentedLine() bool {
return true return true
} }

func preserveLeadingTabInCodeBlock(segment *text.Segment, reader text.Reader, indent int) {
offsetWithPadding := reader.LineOffset() + indent
sl, ss := reader.Position()
reader.SetPosition(sl, text.NewSegment(ss.Start-1, ss.Stop))
if offsetWithPadding == reader.LineOffset() {
segment.Padding = 0
segment.Start--
}
reader.SetPosition(sl, ss)
}

+ 7
- 6
vendor/github.com/yuin/goldmark/parser/code_span.go View File

@@ -3,7 +3,6 @@ package parser
import ( import (
"github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/text" "github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
) )


type codeSpanParser struct { type codeSpanParser struct {
@@ -52,9 +51,7 @@ func (s *codeSpanParser) Parse(parent ast.Node, block text.Reader, pc Context) a
} }
} }
} }
if !util.IsBlank(line) {
node.AppendChild(node, ast.NewRawTextSegment(segment))
}
node.AppendChild(node, ast.NewRawTextSegment(segment))
block.AdvanceLine() block.AdvanceLine()
} }
end: end:
@@ -62,11 +59,11 @@ end:
// trim first halfspace and last halfspace // trim first halfspace and last halfspace
segment := node.FirstChild().(*ast.Text).Segment segment := node.FirstChild().(*ast.Text).Segment
shouldTrimmed := true shouldTrimmed := true
if !(!segment.IsEmpty() && block.Source()[segment.Start] == ' ') {
if !(!segment.IsEmpty() && isSpaceOrNewline(block.Source()[segment.Start])) {
shouldTrimmed = false shouldTrimmed = false
} }
segment = node.LastChild().(*ast.Text).Segment segment = node.LastChild().(*ast.Text).Segment
if !(!segment.IsEmpty() && block.Source()[segment.Stop-1] == ' ') {
if !(!segment.IsEmpty() && isSpaceOrNewline(block.Source()[segment.Stop-1])) {
shouldTrimmed = false shouldTrimmed = false
} }
if shouldTrimmed { if shouldTrimmed {
@@ -81,3 +78,7 @@ end:
} }
return node return node
} }

func isSpaceOrNewline(c byte) bool {
return c == ' ' || c == '\n'
}

+ 11
- 15
vendor/github.com/yuin/goldmark/parser/delimiter.go View File

@@ -3,7 +3,6 @@ package parser
import ( import (
"fmt" "fmt"
"strings" "strings"
"unicode"


"github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/text" "github.com/yuin/goldmark/text"
@@ -31,11 +30,11 @@ type Delimiter struct {
Segment text.Segment Segment text.Segment


// CanOpen is set true if this delimiter can open a span for a new node. // CanOpen is set true if this delimiter can open a span for a new node.
// See https://spec.commonmark.org/0.29/#can-open-emphasis for details.
// See https://spec.commonmark.org/0.30/#can-open-emphasis for details.
CanOpen bool CanOpen bool


// CanClose is set true if this delimiter can close a span for a new node. // CanClose is set true if this delimiter can close a span for a new node.
// See https://spec.commonmark.org/0.29/#can-open-emphasis for details.
// See https://spec.commonmark.org/0.30/#can-open-emphasis for details.
CanClose bool CanClose bool


// Length is a remaining length of this delimiter. // Length is a remaining length of this delimiter.
@@ -128,10 +127,10 @@ func ScanDelimiter(line []byte, before rune, min int, processor DelimiterProcess
} }


canOpen, canClose := false, false canOpen, canClose := false, false
beforeIsPunctuation := unicode.IsPunct(before)
beforeIsWhitespace := unicode.IsSpace(before)
afterIsPunctuation := unicode.IsPunct(after)
afterIsWhitespace := unicode.IsSpace(after)
beforeIsPunctuation := util.IsPunctRune(before)
beforeIsWhitespace := util.IsSpaceRune(before)
afterIsPunctuation := util.IsPunctRune(after)
afterIsWhitespace := util.IsSpaceRune(after)


isLeft := !afterIsWhitespace && isLeft := !afterIsWhitespace &&
(!afterIsPunctuation || beforeIsWhitespace || beforeIsPunctuation) (!afterIsPunctuation || beforeIsWhitespace || beforeIsPunctuation)
@@ -163,15 +162,11 @@ func ProcessDelimiters(bottom ast.Node, pc Context) {
var closer *Delimiter var closer *Delimiter
if bottom != nil { if bottom != nil {
if bottom != lastDelimiter { if bottom != lastDelimiter {
for c := lastDelimiter.PreviousSibling(); c != nil; {
for c := lastDelimiter.PreviousSibling(); c != nil && c != bottom; {
if d, ok := c.(*Delimiter); ok { if d, ok := c.(*Delimiter); ok {
closer = d closer = d
} }
prev := c.PreviousSibling()
if prev == bottom {
break
}
c = prev
c = c.PreviousSibling()
} }
} }
} else { } else {
@@ -190,7 +185,7 @@ func ProcessDelimiters(bottom ast.Node, pc Context) {
found := false found := false
maybeOpener := false maybeOpener := false
var opener *Delimiter var opener *Delimiter
for opener = closer.PreviousDelimiter; opener != nil; opener = opener.PreviousDelimiter {
for opener = closer.PreviousDelimiter; opener != nil && opener != bottom; opener = opener.PreviousDelimiter {
if opener.CanOpen && opener.Processor.CanOpenCloser(opener, closer) { if opener.CanOpen && opener.Processor.CanOpenCloser(opener, closer) {
maybeOpener = true maybeOpener = true
consume = opener.CalcComsumption(closer) consume = opener.CalcComsumption(closer)
@@ -201,10 +196,11 @@ func ProcessDelimiters(bottom ast.Node, pc Context) {
} }
} }
if !found { if !found {
next := closer.NextDelimiter
if !maybeOpener && !closer.CanOpen { if !maybeOpener && !closer.CanOpen {
pc.RemoveDelimiter(closer) pc.RemoveDelimiter(closer)
} }
closer = closer.NextDelimiter
closer = next
continue continue
} }
opener.ConsumeCharacters(consume) opener.ConsumeCharacters(consume)


+ 13
- 2
vendor/github.com/yuin/goldmark/parser/fcode_block.go View File

@@ -71,6 +71,7 @@ func (b *fencedCodeBlockParser) Open(parent ast.Node, reader text.Reader, pc Con
func (b *fencedCodeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context) State { func (b *fencedCodeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
line, segment := reader.PeekLine() line, segment := reader.PeekLine()
fdata := pc.Get(fencedCodeBlockInfoKey).(*fenceData) fdata := pc.Get(fencedCodeBlockInfoKey).(*fenceData)

w, pos := util.IndentWidth(line, reader.LineOffset()) w, pos := util.IndentWidth(line, reader.LineOffset())
if w < 4 { if w < 4 {
i := pos i := pos
@@ -86,9 +87,19 @@ func (b *fencedCodeBlockParser) Continue(node ast.Node, reader text.Reader, pc C
return Close return Close
} }
} }
pos, padding := util.DedentPositionPadding(line, reader.LineOffset(), segment.Padding, fdata.indent)

pos, padding := util.IndentPositionPadding(line, reader.LineOffset(), segment.Padding, fdata.indent)
if pos < 0 {
pos = util.FirstNonSpacePosition(line)
if pos < 0 {
pos = 0
}
padding = 0
}
seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding) seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding)
// if code block line starts with a tab, keep a tab as it is.
if padding != 0 {
preserveLeadingTabInCodeBlock(&seg, reader, fdata.indent)
}
node.Lines().Append(seg) node.Lines().Append(seg)
reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding) reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding)
return Continue | NoChildren return Continue | NoChildren


+ 6
- 6
vendor/github.com/yuin/goldmark/parser/html_block.go View File

@@ -76,8 +76,8 @@ var allowedBlockTags = map[string]bool{
"ul": true, "ul": true,
} }


var htmlBlockType1OpenRegexp = regexp.MustCompile(`(?i)^[ ]{0,3}<(script|pre|style)(?:\s.*|>.*|/>.*|)\n?$`)
var htmlBlockType1CloseRegexp = regexp.MustCompile(`(?i)^.*</(?:script|pre|style)>.*`)
var htmlBlockType1OpenRegexp = regexp.MustCompile(`(?i)^[ ]{0,3}<(script|pre|style|textarea)(?:\s.*|>.*|/>.*|)(?:\r\n|\n)?$`)
var htmlBlockType1CloseRegexp = regexp.MustCompile(`(?i)^.*</(?:script|pre|style|textarea)>.*`)


var htmlBlockType2OpenRegexp = regexp.MustCompile(`^[ ]{0,3}<!\-\-`) var htmlBlockType2OpenRegexp = regexp.MustCompile(`^[ ]{0,3}<!\-\-`)
var htmlBlockType2Close = []byte{'-', '-', '>'} var htmlBlockType2Close = []byte{'-', '-', '>'}
@@ -85,15 +85,15 @@ var htmlBlockType2Close = []byte{'-', '-', '>'}
var htmlBlockType3OpenRegexp = regexp.MustCompile(`^[ ]{0,3}<\?`) var htmlBlockType3OpenRegexp = regexp.MustCompile(`^[ ]{0,3}<\?`)
var htmlBlockType3Close = []byte{'?', '>'} var htmlBlockType3Close = []byte{'?', '>'}


var htmlBlockType4OpenRegexp = regexp.MustCompile(`^[ ]{0,3}<![A-Z]+.*\n?$`)
var htmlBlockType4OpenRegexp = regexp.MustCompile(`^[ ]{0,3}<![A-Z]+.*(?:\r\n|\n)?$`)
var htmlBlockType4Close = []byte{'>'} var htmlBlockType4Close = []byte{'>'}


var htmlBlockType5OpenRegexp = regexp.MustCompile(`^[ ]{0,3}<\!\[CDATA\[`) var htmlBlockType5OpenRegexp = regexp.MustCompile(`^[ ]{0,3}<\!\[CDATA\[`)
var htmlBlockType5Close = []byte{']', ']', '>'} var htmlBlockType5Close = []byte{']', ']', '>'}


var htmlBlockType6Regexp = regexp.MustCompile(`^[ ]{0,3}</?([a-zA-Z0-9]+)(?:\s.*|>.*|/>.*|)\n?$`)
var htmlBlockType6Regexp = regexp.MustCompile(`^[ ]{0,3}<(?:/[ ]*)?([a-zA-Z]+[a-zA-Z0-9\-]*)(?:[ ].*|>.*|/>.*|)(?:\r\n|\n)?$`)


var htmlBlockType7Regexp = regexp.MustCompile(`^[ ]{0,3}<(/)?([a-zA-Z0-9]+)(` + attributePattern + `*)(:?>|/>)\s*\n?$`)
var htmlBlockType7Regexp = regexp.MustCompile(`^[ ]{0,3}<(/[ ]*)?([a-zA-Z]+[a-zA-Z0-9\-]*)(` + attributePattern + `*)[ ]*(?:>|/>)[ ]*(?:\r\n|\n)?$`)


type htmlBlockParser struct { type htmlBlockParser struct {
} }
@@ -201,7 +201,7 @@ func (b *htmlBlockParser) Continue(node ast.Node, reader text.Reader, pc Context
} }
if bytes.Contains(line, closurePattern) { if bytes.Contains(line, closurePattern) {
htmlBlock.ClosureLine = segment htmlBlock.ClosureLine = segment
reader.Advance(segment.Len() - 1)
reader.Advance(segment.Len())
return Close return Close
} }




+ 65
- 43
vendor/github.com/yuin/goldmark/parser/link.go View File

@@ -2,7 +2,6 @@ package parser


import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"


"github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/ast"
@@ -49,6 +48,13 @@ func (s *linkLabelState) Kind() ast.NodeKind {
return kindLinkLabelState return kindLinkLabelState
} }


func linkLabelStateLength(v *linkLabelState) int {
if v == nil || v.Last == nil || v.First == nil {
return 0
}
return v.Last.Segment.Stop - v.First.Segment.Start
}

func pushLinkLabelState(pc Context, v *linkLabelState) { func pushLinkLabelState(pc Context, v *linkLabelState) {
tlist := pc.Get(linkLabelStateKey) tlist := pc.Get(linkLabelStateKey)
var list *linkLabelState var list *linkLabelState
@@ -113,8 +119,6 @@ func (s *linkParser) Trigger() []byte {
return []byte{'!', '[', ']'} return []byte{'!', '[', ']'}
} }


var linkDestinationRegexp = regexp.MustCompile(`\s*([^\s].+)`)
var linkTitleRegexp = regexp.MustCompile(`\s+(\)|["'\(].+)`)
var linkBottom = NewContextKey() var linkBottom = NewContextKey()


func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.Node { func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.Node {
@@ -143,7 +147,14 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
} }
block.Advance(1) block.Advance(1)
removeLinkLabelState(pc, last) removeLinkLabelState(pc, last)
if s.containsLink(last) { // a link in a link text is not allowed
// CommonMark spec says:
// > A link label can have at most 999 characters inside the square brackets.
if linkLabelStateLength(tlist.(*linkLabelState)) > 998 {
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
return nil
}

if !last.IsImage && s.containsLink(last) { // a link in a link text is not allowed
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment) ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
return nil return nil
} }
@@ -167,6 +178,13 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
block.SetPosition(l, pos) block.SetPosition(l, pos)
ssegment := text.NewSegment(last.Segment.Stop, segment.Start) ssegment := text.NewSegment(last.Segment.Stop, segment.Start)
maybeReference := block.Value(ssegment) maybeReference := block.Value(ssegment)
// CommonMark spec says:
// > A link label can have at most 999 characters inside the square brackets.
if len(maybeReference) > 999 {
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
return nil
}

ref, ok := pc.Reference(util.ToLinkReference(maybeReference)) ref, ok := pc.Reference(util.ToLinkReference(maybeReference))
if !ok { if !ok {
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment) ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
@@ -185,15 +203,17 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
return link return link
} }


func (s *linkParser) containsLink(last *linkLabelState) bool {
if last.IsImage {
func (s *linkParser) containsLink(n ast.Node) bool {
if n == nil {
return false return false
} }
var c ast.Node
for c = last; c != nil; c = c.NextSibling() {
for c := n; c != nil; c = c.NextSibling() {
if _, ok := c.(*ast.Link); ok { if _, ok := c.(*ast.Link); ok {
return true return true
} }
if s.containsLink(c.FirstChild()) {
return true
}
} }
return false return false
} }
@@ -224,21 +244,38 @@ func (s *linkParser) processLinkLabel(parent ast.Node, link *ast.Link, last *lin
} }
} }


var linkFindClosureOptions text.FindClosureOptions = text.FindClosureOptions{
Nesting: false,
Newline: true,
Advance: true,
}

func (s *linkParser) parseReferenceLink(parent ast.Node, last *linkLabelState, block text.Reader, pc Context) (*ast.Link, bool) { func (s *linkParser) parseReferenceLink(parent ast.Node, last *linkLabelState, block text.Reader, pc Context) (*ast.Link, bool) {
_, orgpos := block.Position() _, orgpos := block.Position()
block.Advance(1) // skip '[' block.Advance(1) // skip '['
line, segment := block.PeekLine()
endIndex := util.FindClosure(line, '[', ']', false, true)
if endIndex < 0 {
segments, found := block.FindClosure('[', ']', linkFindClosureOptions)
if !found {
return nil, false return nil, false
} }


block.Advance(endIndex + 1)
ssegment := segment.WithStop(segment.Start + endIndex)
maybeReference := block.Value(ssegment)
var maybeReference []byte
if segments.Len() == 1 { // avoid allocate a new byte slice
maybeReference = block.Value(segments.At(0))
} else {
maybeReference = []byte{}
for i := 0; i < segments.Len(); i++ {
s := segments.At(i)
maybeReference = append(maybeReference, block.Value(s)...)
}
}
if util.IsBlank(maybeReference) { // collapsed reference link if util.IsBlank(maybeReference) { // collapsed reference link
ssegment = text.NewSegment(last.Segment.Stop, orgpos.Start-1)
maybeReference = block.Value(ssegment)
s := text.NewSegment(last.Segment.Stop, orgpos.Start-1)
maybeReference = block.Value(s)
}
// CommonMark spec says:
// > A link label can have at most 999 characters inside the square brackets.
if len(maybeReference) > 999 {
return nil, true
} }


ref, ok := pc.Reference(util.ToLinkReference(maybeReference)) ref, ok := pc.Reference(util.ToLinkReference(maybeReference))
@@ -293,20 +330,17 @@ func (s *linkParser) parseLink(parent ast.Node, last *linkLabelState, block text
func parseLinkDestination(block text.Reader) ([]byte, bool) { func parseLinkDestination(block text.Reader) ([]byte, bool) {
block.SkipSpaces() block.SkipSpaces()
line, _ := block.PeekLine() line, _ := block.PeekLine()
buf := []byte{}
if block.Peek() == '<' { if block.Peek() == '<' {
i := 1 i := 1
for i < len(line) { for i < len(line) {
c := line[i] c := line[i]
if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) { if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) {
buf = append(buf, '\\', line[i+1])
i += 2 i += 2
continue continue
} else if c == '>' { } else if c == '>' {
block.Advance(i + 1) block.Advance(i + 1)
return line[1:i], true return line[1:i], true
} }
buf = append(buf, c)
i++ i++
} }
return nil, false return nil, false
@@ -316,7 +350,6 @@ func parseLinkDestination(block text.Reader) ([]byte, bool) {
for i < len(line) { for i < len(line) {
c := line[i] c := line[i]
if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) { if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) {
buf = append(buf, '\\', line[i+1])
i += 2 i += 2
continue continue
} else if c == '(' { } else if c == '(' {
@@ -329,7 +362,6 @@ func parseLinkDestination(block text.Reader) ([]byte, bool) {
} else if util.IsSpace(c) { } else if util.IsSpace(c) {
break break
} }
buf = append(buf, c)
i++ i++
} }
block.Advance(i) block.Advance(i)
@@ -346,34 +378,24 @@ func parseLinkTitle(block text.Reader) ([]byte, bool) {
if opener == '(' { if opener == '(' {
closer = ')' closer = ')'
} }
savedLine, savedPosition := block.Position()
var title []byte
for i := 0; ; i++ {
line, _ := block.PeekLine()
if line == nil {
block.SetPosition(savedLine, savedPosition)
return nil, false
}
offset := 0
if i == 0 {
offset = 1
}
pos := util.FindClosure(line[offset:], opener, closer, false, true)
if pos < 0 {
title = append(title, line[offset:]...)
block.AdvanceLine()
continue
block.Advance(1)
segments, found := block.FindClosure(opener, closer, linkFindClosureOptions)
if found {
if segments.Len() == 1 {
return block.Value(segments.At(0)), true
} }
pos += offset + 1 // 1: closer
block.Advance(pos)
if i == 0 { // avoid allocating new slice
return line[offset : pos-1], true
var title []byte
for i := 0; i < segments.Len(); i++ {
s := segments.At(i)
title = append(title, block.Value(s)...)
} }
return append(title, line[offset:pos-1]...), true
return title, true
} }
return nil, false
} }


func (s *linkParser) CloseBlock(parent ast.Node, block text.Reader, pc Context) { func (s *linkParser) CloseBlock(parent ast.Node, block text.Reader, pc Context) {
pc.Set(linkBottom, nil)
tlist := pc.Get(linkLabelStateKey) tlist := pc.Get(linkLabelStateKey)
if tlist == nil { if tlist == nil {
return return


+ 31
- 42
vendor/github.com/yuin/goldmark/parser/link_ref.go View File

@@ -52,7 +52,7 @@ func (p *linkReferenceParagraphTransformer) Transform(node *ast.Paragraph, reade


func parseLinkReferenceDefinition(block text.Reader, pc Context) (int, int) { func parseLinkReferenceDefinition(block text.Reader, pc Context) (int, int) {
block.SkipSpaces() block.SkipSpaces()
line, segment := block.PeekLine()
line, _ := block.PeekLine()
if line == nil { if line == nil {
return -1, -1 return -1, -1
} }
@@ -67,39 +67,33 @@ func parseLinkReferenceDefinition(block text.Reader, pc Context) (int, int) {
if line[pos] != '[' { if line[pos] != '[' {
return -1, -1 return -1, -1
} }
open := segment.Start + pos + 1
closes := -1
block.Advance(pos + 1) block.Advance(pos + 1)
for {
line, segment = block.PeekLine()
if line == nil {
return -1, -1
}
closure := util.FindClosure(line, '[', ']', false, false)
if closure > -1 {
closes = segment.Start + closure
next := closure + 1
if next >= len(line) || line[next] != ':' {
return -1, -1
}
block.Advance(next + 1)
break
segments, found := block.FindClosure('[', ']', linkFindClosureOptions)
if !found {
return -1, -1
}
var label []byte
if segments.Len() == 1 {
label = block.Value(segments.At(0))
} else {
for i := 0; i < segments.Len(); i++ {
s := segments.At(i)
label = append(label, block.Value(s)...)
} }
block.AdvanceLine()
} }
if closes < 0 {
if util.IsBlank(label) {
return -1, -1 return -1, -1
} }
label := block.Value(text.NewSegment(open, closes))
if util.IsBlank(label) {
if block.Peek() != ':' {
return -1, -1 return -1, -1
} }
block.Advance(1)
block.SkipSpaces() block.SkipSpaces()
destination, ok := parseLinkDestination(block) destination, ok := parseLinkDestination(block)
if !ok { if !ok {
return -1, -1 return -1, -1
} }
line, segment = block.PeekLine()
line, _ = block.PeekLine()
isNewLine := line == nil || util.IsBlank(line) isNewLine := line == nil || util.IsBlank(line)


endLine, _ := block.Position() endLine, _ := block.Position()
@@ -117,45 +111,40 @@ func parseLinkReferenceDefinition(block text.Reader, pc Context) (int, int) {
return -1, -1 return -1, -1
} }
block.Advance(1) block.Advance(1)
open = -1
closes = -1
closer := opener closer := opener
if opener == '(' { if opener == '(' {
closer = ')' closer = ')'
} }
for {
line, segment = block.PeekLine()
if line == nil {
segments, found = block.FindClosure(opener, closer, linkFindClosureOptions)
if !found {
if !isNewLine {
return -1, -1 return -1, -1
} }
if open < 0 {
open = segment.Start
}
closure := util.FindClosure(line, opener, closer, false, true)
if closure > -1 {
closes = segment.Start + closure
block.Advance(closure + 1)
break
}
ref := NewReference(label, destination, nil)
pc.AddReference(ref)
block.AdvanceLine() block.AdvanceLine()
return startLine, endLine + 1
} }
if closes < 0 {
return -1, -1
var title []byte
if segments.Len() == 1 {
title = block.Value(segments.At(0))
} else {
for i := 0; i < segments.Len(); i++ {
s := segments.At(i)
title = append(title, block.Value(s)...)
}
} }


line, segment = block.PeekLine()
line, _ = block.PeekLine()
if line != nil && !util.IsBlank(line) { if line != nil && !util.IsBlank(line) {
if !isNewLine { if !isNewLine {
return -1, -1 return -1, -1
} }
title := block.Value(text.NewSegment(open, closes))
ref := NewReference(label, destination, title) ref := NewReference(label, destination, title)
pc.AddReference(ref) pc.AddReference(ref)
return startLine, endLine return startLine, endLine
} }


title := block.Value(text.NewSegment(open, closes))

endLine, _ = block.Position() endLine, _ = block.Position()
ref := NewReference(label, destination, title) ref := NewReference(label, destination, title)
pc.AddReference(ref) pc.AddReference(ref)


+ 46
- 10
vendor/github.com/yuin/goldmark/parser/list.go View File

@@ -1,10 +1,11 @@
package parser package parser


import ( import (
"strconv"

"github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/text" "github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util" "github.com/yuin/goldmark/util"
"strconv"
) )


type listItemType int type listItemType int
@@ -15,6 +16,10 @@ const (
orderedList orderedList
) )


var skipListParserKey = NewContextKey()
var emptyListItemWithBlankLines = NewContextKey()
var listItemFlagValue interface{} = true

// Same as // Same as
// `^(([ ]*)([\-\*\+]))(\s+.*)?\n?$`.FindSubmatchIndex or // `^(([ ]*)([\-\*\+]))(\s+.*)?\n?$`.FindSubmatchIndex or
// `^(([ ]*)(\d{1,9}[\.\)]))(\s+.*)?\n?$`.FindSubmatchIndex // `^(([ ]*)(\d{1,9}[\.\)]))(\s+.*)?\n?$`.FindSubmatchIndex
@@ -122,8 +127,8 @@ func (b *listParser) Trigger() []byte {


func (b *listParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) { func (b *listParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
last := pc.LastOpenedBlock().Node last := pc.LastOpenedBlock().Node
if _, lok := last.(*ast.List); lok || pc.Get(skipListParser) != nil {
pc.Set(skipListParser, nil)
if _, lok := last.(*ast.List); lok || pc.Get(skipListParserKey) != nil {
pc.Set(skipListParserKey, nil)
return nil, NoChildren return nil, NoChildren
} }
line, _ := reader.PeekLine() line, _ := reader.PeekLine()
@@ -143,7 +148,7 @@ func (b *listParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.
return nil, NoChildren return nil, NoChildren
} }
//an empty list item cannot interrupt a paragraph: //an empty list item cannot interrupt a paragraph:
if match[5]-match[4] == 1 {
if match[4] < 0 || util.IsBlank(line[match[4]:match[5]]) {
return nil, NoChildren return nil, NoChildren
} }
} }
@@ -153,6 +158,7 @@ func (b *listParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.
if start > -1 { if start > -1 {
node.Start = start node.Start = start
} }
pc.Set(emptyListItemWithBlankLines, nil)
return node, HasChildren return node, HasChildren
} }


@@ -160,9 +166,8 @@ func (b *listParser) Continue(node ast.Node, reader text.Reader, pc Context) Sta
list := node.(*ast.List) list := node.(*ast.List)
line, _ := reader.PeekLine() line, _ := reader.PeekLine()
if util.IsBlank(line) { if util.IsBlank(line) {
// A list item can begin with at most one blank line
if node.ChildCount() == 1 && node.LastChild().ChildCount() == 0 {
return Close
if node.LastChild().ChildCount() == 0 {
pc.Set(emptyListItemWithBlankLines, listItemFlagValue)
} }
return Continue | HasChildren return Continue | HasChildren
} }
@@ -175,10 +180,23 @@ func (b *listParser) Continue(node ast.Node, reader text.Reader, pc Context) Sta
// - a // - a
// - b <--- current line // - b <--- current line
// it maybe a new child of the list. // it maybe a new child of the list.
//
// Empty list items can have multiple blanklines
//
// - <--- 1st item is an empty thus "offset" is unknown
//
//
// - <--- current line
//
// -> 1 list with 2 blank items
//
// So if the last item is an empty, it maybe a new child of the list.
//
offset := lastOffset(node) offset := lastOffset(node)
lastIsEmpty := node.LastChild().ChildCount() == 0
indent, _ := util.IndentWidth(line, reader.LineOffset()) indent, _ := util.IndentWidth(line, reader.LineOffset())


if indent < offset {
if indent < offset || lastIsEmpty {
if indent < 4 { if indent < 4 {
match, typ := matchesListItem(line, false) // may have a leading spaces more than 3 match, typ := matchesListItem(line, false) // may have a leading spaces more than 3
if typ != notList && match[1]-offset < 4 { if typ != notList && match[1]-offset < 4 {
@@ -200,10 +218,27 @@ func (b *listParser) Continue(node ast.Node, reader text.Reader, pc Context) Sta
return Close return Close
} }
} }

return Continue | HasChildren return Continue | HasChildren
} }
} }
if !lastIsEmpty {
return Close
}
}

if lastIsEmpty && indent < offset {
return Close
}

// Non empty items can not exist next to an empty list item
// with blank lines. So we need to close the current list
//
// -
//
// foo
//
// -> 1 list with 1 blank items and 1 paragraph
if pc.Get(emptyListItemWithBlankLines) != nil {
return Close return Close
} }
return Continue | HasChildren return Continue | HasChildren
@@ -230,8 +265,9 @@ func (b *listParser) Close(node ast.Node, reader text.Reader, pc Context) {


if list.IsTight { if list.IsTight {
for child := node.FirstChild(); child != nil; child = child.NextSibling() { for child := node.FirstChild(); child != nil; child = child.NextSibling() {
for gc := child.FirstChild(); gc != nil; gc = gc.NextSibling() {
for gc := child.FirstChild(); gc != nil; {
paragraph, ok := gc.(*ast.Paragraph) paragraph, ok := gc.(*ast.Paragraph)
gc = gc.NextSibling()
if ok { if ok {
textBlock := ast.NewTextBlock() textBlock := ast.NewTextBlock()
textBlock.SetLines(paragraph.Lines()) textBlock.SetLines(paragraph.Lines())


+ 13
- 8
vendor/github.com/yuin/goldmark/parser/list_item.go View File

@@ -17,9 +17,6 @@ func NewListItemParser() BlockParser {
return defaultListItemParser return defaultListItemParser
} }


var skipListParser = NewContextKey()
var skipListParserValue interface{} = true

func (b *listItemParser) Trigger() []byte { func (b *listItemParser) Trigger() []byte {
return []byte{'-', '+', '*', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} return []byte{'-', '+', '*', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
} }
@@ -38,9 +35,12 @@ func (b *listItemParser) Open(parent ast.Node, reader text.Reader, pc Context) (
if match[1]-offset > 3 { if match[1]-offset > 3 {
return nil, NoChildren return nil, NoChildren
} }

pc.Set(emptyListItemWithBlankLines, nil)

itemOffset := calcListOffset(line, match) itemOffset := calcListOffset(line, match)
node := ast.NewListItem(match[3] + itemOffset) node := ast.NewListItem(match[3] + itemOffset)
if match[4] < 0 || match[5]-match[4] == 1 {
if match[4] < 0 || util.IsBlank(line[match[4]:match[5]]) {
return node, NoChildren return node, NoChildren
} }


@@ -53,18 +53,23 @@ func (b *listItemParser) Open(parent ast.Node, reader text.Reader, pc Context) (
func (b *listItemParser) Continue(node ast.Node, reader text.Reader, pc Context) State { func (b *listItemParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
line, _ := reader.PeekLine() line, _ := reader.PeekLine()
if util.IsBlank(line) { if util.IsBlank(line) {
reader.Advance(len(line) - 1)
return Continue | HasChildren return Continue | HasChildren
} }


indent, _ := util.IndentWidth(line, reader.LineOffset())
offset := lastOffset(node.Parent()) offset := lastOffset(node.Parent())
if indent < offset && indent < 4 {
isEmpty := node.ChildCount() == 0
indent, _ := util.IndentWidth(line, reader.LineOffset())
if (isEmpty || indent < offset) && indent < 4 {
_, typ := matchesListItem(line, true) _, typ := matchesListItem(line, true)
// new list item found // new list item found
if typ != notList { if typ != notList {
pc.Set(skipListParser, skipListParserValue)
pc.Set(skipListParserKey, listItemFlagValue)
return Close
}
if !isEmpty {
return Close
} }
return Close
} }
pos, padding := util.IndentPosition(line, reader.LineOffset(), offset) pos, padding := util.IndentPosition(line, reader.LineOffset(), offset)
reader.AdvanceAndSetPadding(pos, padding) reader.AdvanceAndSetPadding(pos, padding)


+ 41
- 16
vendor/github.com/yuin/goldmark/parser/parser.go View File

@@ -138,6 +138,9 @@ type Context interface {
// Get returns a value associated with the given key. // Get returns a value associated with the given key.
Get(ContextKey) interface{} Get(ContextKey) interface{}


// ComputeIfAbsent computes a value if a value associated with the given key is absent and returns the value.
ComputeIfAbsent(ContextKey, func() interface{}) interface{}

// Set sets the given value to the context. // Set sets the given value to the context.
Set(ContextKey, interface{}) Set(ContextKey, interface{})


@@ -252,6 +255,15 @@ func (p *parseContext) Get(key ContextKey) interface{} {
return p.store[key] return p.store[key]
} }


func (p *parseContext) ComputeIfAbsent(key ContextKey, f func() interface{}) interface{} {
v := p.store[key]
if v == nil {
v = f()
p.store[key] = v
}
return v
}

func (p *parseContext) Set(key ContextKey, value interface{}) { func (p *parseContext) Set(key ContextKey, value interface{}) {
p.store[key] = value p.store[key] = value
} }
@@ -1103,6 +1115,12 @@ func (p *parser) walkBlock(block ast.Node, cb func(node ast.Node)) {
cb(block) cb(block)
} }


const (
lineBreakHard uint8 = 1 << iota
lineBreakSoft
lineBreakVisible
)

func (p *parser) parseBlock(block text.BlockReader, parent ast.Node, pc Context) { func (p *parser) parseBlock(block text.BlockReader, parent ast.Node, pc Context) {
if parent.IsRaw() { if parent.IsRaw() {
return return
@@ -1117,21 +1135,25 @@ func (p *parser) parseBlock(block text.BlockReader, parent ast.Node, pc Context)
break break
} }
lineLength := len(line) lineLength := len(line)
hardlineBreak := false
softLinebreak := line[lineLength-1] == '\n'
if lineLength >= 2 && line[lineLength-2] == '\\' && softLinebreak { // ends with \\n
var lineBreakFlags uint8 = 0
hasNewLine := line[lineLength-1] == '\n'
if ((lineLength >= 3 && line[lineLength-2] == '\\' && line[lineLength-3] != '\\') || (lineLength == 2 && line[lineLength-2] == '\\')) && hasNewLine { // ends with \\n
lineLength -= 2 lineLength -= 2
hardlineBreak = true

} else if lineLength >= 3 && line[lineLength-3] == '\\' && line[lineLength-2] == '\r' && softLinebreak { // ends with \\r\n
lineBreakFlags |= lineBreakHard | lineBreakVisible
} else if ((lineLength >= 4 && line[lineLength-3] == '\\' && line[lineLength-2] == '\r' && line[lineLength-4] != '\\') || (lineLength == 3 && line[lineLength-3] == '\\' && line[lineLength-2] == '\r')) && hasNewLine { // ends with \\r\n
lineLength -= 3 lineLength -= 3
hardlineBreak = true
} else if lineLength >= 3 && line[lineLength-3] == ' ' && line[lineLength-2] == ' ' && softLinebreak { // ends with [space][space]\n
lineBreakFlags |= lineBreakHard | lineBreakVisible
} else if lineLength >= 3 && line[lineLength-3] == ' ' && line[lineLength-2] == ' ' && hasNewLine { // ends with [space][space]\n
lineLength -= 3 lineLength -= 3
hardlineBreak = true
} else if lineLength >= 4 && line[lineLength-4] == ' ' && line[lineLength-3] == ' ' && line[lineLength-2] == '\r' && softLinebreak { // ends with [space][space]\r\n
lineBreakFlags |= lineBreakHard
} else if lineLength >= 4 && line[lineLength-4] == ' ' && line[lineLength-3] == ' ' && line[lineLength-2] == '\r' && hasNewLine { // ends with [space][space]\r\n
lineLength -= 4 lineLength -= 4
hardlineBreak = true
lineBreakFlags |= lineBreakHard
} else if hasNewLine {
// If the line ends with a newline character, but it is not a hardlineBreak, then it is a softLinebreak
// If the line ends with a hardlineBreak, then it cannot end with a softLinebreak
// See https://spec.commonmark.org/0.30/#soft-line-breaks
lineBreakFlags |= lineBreakSoft
} }


l, startPosition := block.Position() l, startPosition := block.Position()
@@ -1195,11 +1217,14 @@ func (p *parser) parseBlock(block text.BlockReader, parent ast.Node, pc Context)
continue continue
} }
diff := startPosition.Between(currentPosition) diff := startPosition.Between(currentPosition)
stop := diff.Stop
rest := diff.WithStop(stop)
text := ast.NewTextSegment(rest.TrimRightSpace(source))
text.SetSoftLineBreak(softLinebreak)
text.SetHardLineBreak(hardlineBreak)
var text *ast.Text
if lineBreakFlags&(lineBreakHard|lineBreakVisible) == lineBreakHard|lineBreakVisible {
text = ast.NewTextSegment(diff)
} else {
text = ast.NewTextSegment(diff.TrimRightSpace(source))
}
text.SetSoftLineBreak(lineBreakFlags&lineBreakSoft != 0)
text.SetHardLineBreak(lineBreakFlags&lineBreakHard != 0)
parent.AppendChild(parent, text) parent.AppendChild(parent, text)
block.AdvanceLine() block.AdvanceLine()
} }


+ 81
- 26
vendor/github.com/yuin/goldmark/parser/raw_html.go View File

@@ -2,10 +2,11 @@ package parser


import ( import (
"bytes" "bytes"
"regexp"

"github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/text" "github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util" "github.com/yuin/goldmark/util"
"regexp"
) )


type rawHTMLParser struct { type rawHTMLParser struct {
@@ -31,43 +32,101 @@ func (s *rawHTMLParser) Parse(parent ast.Node, block text.Reader, pc Context) as
if len(line) > 2 && line[1] == '/' && util.IsAlphaNumeric(line[2]) { if len(line) > 2 && line[1] == '/' && util.IsAlphaNumeric(line[2]) {
return s.parseMultiLineRegexp(closeTagRegexp, block, pc) return s.parseMultiLineRegexp(closeTagRegexp, block, pc)
} }
if bytes.HasPrefix(line, []byte("<!--")) {
return s.parseMultiLineRegexp(commentRegexp, block, pc)
if bytes.HasPrefix(line, openComment) {
return s.parseComment(block, pc)
} }
if bytes.HasPrefix(line, []byte("<?")) {
return s.parseSingleLineRegexp(processingInstructionRegexp, block, pc)
if bytes.HasPrefix(line, openProcessingInstruction) {
return s.parseUntil(block, closeProcessingInstruction, pc)
} }
if len(line) > 2 && line[1] == '!' && line[2] >= 'A' && line[2] <= 'Z' { if len(line) > 2 && line[1] == '!' && line[2] >= 'A' && line[2] <= 'Z' {
return s.parseSingleLineRegexp(declRegexp, block, pc)
return s.parseUntil(block, closeDecl, pc)
} }
if bytes.HasPrefix(line, []byte("<![CDATA[")) {
return s.parseMultiLineRegexp(cdataRegexp, block, pc)
if bytes.HasPrefix(line, openCDATA) {
return s.parseUntil(block, closeCDATA, pc)
} }
return nil return nil
} }


var tagnamePattern = `([A-Za-z][A-Za-z0-9-]*)` var tagnamePattern = `([A-Za-z][A-Za-z0-9-]*)`
var attributePattern = `(?:\s+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:\s*=\s*(?:[^\"'=<>` + "`" + `\x00-\x20]+|'[^']*'|"[^"]*"))?)`
var openTagRegexp = regexp.MustCompile("^<" + tagnamePattern + attributePattern + `*\s*/?>`)

var attributePattern = `(?:[\r\n \t]+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:[\r\n \t]*=[\r\n \t]*(?:[^\"'=<>` + "`" + `\x00-\x20]+|'[^']*'|"[^"]*"))?)`
var openTagRegexp = regexp.MustCompile("^<" + tagnamePattern + attributePattern + `*[ \t]*/?>`)
var closeTagRegexp = regexp.MustCompile("^</" + tagnamePattern + `\s*>`) var closeTagRegexp = regexp.MustCompile("^</" + tagnamePattern + `\s*>`)
var commentRegexp = regexp.MustCompile(`^<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->`)
var processingInstructionRegexp = regexp.MustCompile(`^(?:<\?).*?(?:\?>)`)
var declRegexp = regexp.MustCompile(`^<![A-Z]+\s+[^>]*>`)
var cdataRegexp = regexp.MustCompile(`<!\[CDATA\[[\s\S]*?\]\]>`)


func (s *rawHTMLParser) parseSingleLineRegexp(reg *regexp.Regexp, block text.Reader, pc Context) ast.Node {
var openProcessingInstruction = []byte("<?")
var closeProcessingInstruction = []byte("?>")
var openCDATA = []byte("<![CDATA[")
var closeCDATA = []byte("]]>")
var closeDecl = []byte(">")
var emptyComment = []byte("<!---->")
var invalidComment1 = []byte("<!-->")
var invalidComment2 = []byte("<!--->")
var openComment = []byte("<!--")
var closeComment = []byte("-->")
var doubleHyphen = []byte("--")

func (s *rawHTMLParser) parseComment(block text.Reader, pc Context) ast.Node {
savedLine, savedSegment := block.Position()
node := ast.NewRawHTML()
line, segment := block.PeekLine() line, segment := block.PeekLine()
match := reg.FindSubmatchIndex(line)
if match == nil {
if bytes.HasPrefix(line, emptyComment) {
node.Segments.Append(segment.WithStop(segment.Start + len(emptyComment)))
block.Advance(len(emptyComment))
return node
}
if bytes.HasPrefix(line, invalidComment1) || bytes.HasPrefix(line, invalidComment2) {
return nil return nil
} }
node := ast.NewRawHTML()
node.Segments.Append(segment.WithStop(segment.Start + match[1]))
block.Advance(match[1])
return node
offset := len(openComment)
line = line[offset:]
for {
hindex := bytes.Index(line, doubleHyphen)
if hindex > -1 {
hindex += offset
}
index := bytes.Index(line, closeComment) + offset
if index > -1 && hindex == index {
if index == 0 || len(line) < 2 || line[index-offset-1] != '-' {
node.Segments.Append(segment.WithStop(segment.Start + index + len(closeComment)))
block.Advance(index + len(closeComment))
return node
}
}
if hindex > 0 {
break
}
node.Segments.Append(segment)
block.AdvanceLine()
line, segment = block.PeekLine()
offset = 0
if line == nil {
break
}
}
block.SetPosition(savedLine, savedSegment)
return nil
} }


var dummyMatch = [][]byte{}
func (s *rawHTMLParser) parseUntil(block text.Reader, closer []byte, pc Context) ast.Node {
savedLine, savedSegment := block.Position()
node := ast.NewRawHTML()
for {
line, segment := block.PeekLine()
if line == nil {
break
}
index := bytes.Index(line, closer)
if index > -1 {
node.Segments.Append(segment.WithStop(segment.Start + index + len(closer)))
block.Advance(index + len(closer))
return node
}
node.Segments.Append(segment)
block.AdvanceLine()
}
block.SetPosition(savedLine, savedSegment)
return nil
}


func (s *rawHTMLParser) parseMultiLineRegexp(reg *regexp.Regexp, block text.Reader, pc Context) ast.Node { func (s *rawHTMLParser) parseMultiLineRegexp(reg *regexp.Regexp, block text.Reader, pc Context) ast.Node {
sline, ssegment := block.Position() sline, ssegment := block.Position()
@@ -102,7 +161,3 @@ func (s *rawHTMLParser) parseMultiLineRegexp(reg *regexp.Regexp, block text.Read
} }
return nil return nil
} }

func (s *rawHTMLParser) CloseBlock(parent ast.Node, pc Context) {
// nothing to do
}

+ 49
- 11
vendor/github.com/yuin/goldmark/renderer/html/html.go View File

@@ -198,16 +198,24 @@ func (r *Renderer) writeLines(w util.BufWriter, source []byte, n ast.Node) {
var GlobalAttributeFilter = util.NewBytesFilter( var GlobalAttributeFilter = util.NewBytesFilter(
[]byte("accesskey"), []byte("accesskey"),
[]byte("autocapitalize"), []byte("autocapitalize"),
[]byte("autofocus"),
[]byte("class"), []byte("class"),
[]byte("contenteditable"), []byte("contenteditable"),
[]byte("contextmenu"),
[]byte("dir"), []byte("dir"),
[]byte("draggable"), []byte("draggable"),
[]byte("dropzone"),
[]byte("enterkeyhint"),
[]byte("hidden"), []byte("hidden"),
[]byte("id"), []byte("id"),
[]byte("inert"),
[]byte("inputmode"),
[]byte("is"),
[]byte("itemid"),
[]byte("itemprop"), []byte("itemprop"),
[]byte("itemref"),
[]byte("itemscope"),
[]byte("itemtype"),
[]byte("lang"), []byte("lang"),
[]byte("part"),
[]byte("slot"), []byte("slot"),
[]byte("spellcheck"), []byte("spellcheck"),
[]byte("style"), []byte("style"),
@@ -296,7 +304,7 @@ func (r *Renderer) renderHTMLBlock(w util.BufWriter, source []byte, node ast.Nod
l := n.Lines().Len() l := n.Lines().Len()
for i := 0; i < l; i++ { for i := 0; i < l; i++ {
line := n.Lines().At(i) line := n.Lines().At(i)
_, _ = w.Write(line.Value(source))
r.Writer.SecureWrite(w, line.Value(source))
} }
} else { } else {
_, _ = w.WriteString("<!-- raw HTML omitted -->\n") _, _ = w.WriteString("<!-- raw HTML omitted -->\n")
@@ -305,7 +313,7 @@ func (r *Renderer) renderHTMLBlock(w util.BufWriter, source []byte, node ast.Nod
if n.HasClosure() { if n.HasClosure() {
if r.Unsafe { if r.Unsafe {
closure := n.ClosureLine closure := n.ClosureLine
_, _ = w.Write(closure.Value(source))
r.Writer.SecureWrite(w, closure.Value(source))
} else { } else {
_, _ = w.WriteString("<!-- raw HTML omitted -->\n") _, _ = w.WriteString("<!-- raw HTML omitted -->\n")
} }
@@ -318,6 +326,7 @@ func (r *Renderer) renderHTMLBlock(w util.BufWriter, source []byte, node ast.Nod
var ListAttributeFilter = GlobalAttributeFilter.Extend( var ListAttributeFilter = GlobalAttributeFilter.Extend(
[]byte("start"), []byte("start"),
[]byte("reversed"), []byte("reversed"),
[]byte("type"),
) )


func (r *Renderer) renderList(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { func (r *Renderer) renderList(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
@@ -476,9 +485,7 @@ func (r *Renderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Node, e
value := segment.Value(source) value := segment.Value(source)
if bytes.HasSuffix(value, []byte("\n")) { if bytes.HasSuffix(value, []byte("\n")) {
r.Writer.RawWrite(w, value[:len(value)-1]) r.Writer.RawWrite(w, value[:len(value)-1])
if c != n.LastChild() {
r.Writer.RawWrite(w, []byte(" "))
}
r.Writer.RawWrite(w, []byte(" "))
} else { } else {
r.Writer.RawWrite(w, value) r.Writer.RawWrite(w, value)
} }
@@ -564,7 +571,7 @@ func (r *Renderer) renderImage(w util.BufWriter, source []byte, node ast.Node, e
_, _ = w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true))) _, _ = w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
} }
_, _ = w.WriteString(`" alt="`) _, _ = w.WriteString(`" alt="`)
_, _ = w.Write(n.Text(source))
_, _ = w.Write(util.EscapeHTML(n.Text(source)))
_ = w.WriteByte('"') _ = w.WriteByte('"')
if n.Title != nil { if n.Title != nil {
_, _ = w.WriteString(` title="`) _, _ = w.WriteString(` title="`)
@@ -669,8 +676,13 @@ type Writer interface {
// RawWrite writes the given source to writer without resolving references and // RawWrite writes the given source to writer without resolving references and
// unescaping backslash escaped characters. // unescaping backslash escaped characters.
RawWrite(writer util.BufWriter, source []byte) RawWrite(writer util.BufWriter, source []byte)

// SecureWrite writes the given source to writer with replacing insecure characters.
SecureWrite(writer util.BufWriter, source []byte)
} }


var replacementCharacter = []byte("\ufffd")

type defaultWriter struct { type defaultWriter struct {
} }


@@ -685,6 +697,23 @@ func escapeRune(writer util.BufWriter, r rune) {
_, _ = writer.WriteRune(util.ToValidRune(r)) _, _ = writer.WriteRune(util.ToValidRune(r))
} }


func (d *defaultWriter) SecureWrite(writer util.BufWriter, source []byte) {
n := 0
l := len(source)
for i := 0; i < l; i++ {
if source[i] == '\u0000' {
_, _ = writer.Write(source[i-n : i])
n = 0
_, _ = writer.Write(replacementCharacter)
continue
}
n++
}
if n != 0 {
_, _ = writer.Write(source[l-n:])
}
}

func (d *defaultWriter) RawWrite(writer util.BufWriter, source []byte) { func (d *defaultWriter) RawWrite(writer util.BufWriter, source []byte) {
n := 0 n := 0
l := len(source) l := len(source)
@@ -718,6 +747,13 @@ func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
continue continue
} }
} }
if c == '\x00' {
d.RawWrite(writer, source[n:i])
d.RawWrite(writer, replacementCharacter)
n = i + 1
escaped = false
continue
}
if c == '&' { if c == '&' {
pos := i pos := i
next := i + 1 next := i + 1
@@ -729,7 +765,7 @@ func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
if nnext < limit && nc == 'x' || nc == 'X' { if nnext < limit && nc == 'x' || nc == 'X' {
start := nnext + 1 start := nnext + 1
i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsHexDecimal) i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsHexDecimal)
if ok && i < limit && source[i] == ';' {
if ok && i < limit && source[i] == ';' && i-start < 7 {
v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 16, 32) v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 16, 32)
d.RawWrite(writer, source[n:pos]) d.RawWrite(writer, source[n:pos])
n = i + 1 n = i + 1
@@ -741,7 +777,7 @@ func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
start := nnext start := nnext
i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsNumeric) i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsNumeric)
if ok && i < limit && i-start < 8 && source[i] == ';' { if ok && i < limit && i-start < 8 && source[i] == ';' {
v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 0, 32)
v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 10, 32)
d.RawWrite(writer, source[n:pos]) d.RawWrite(writer, source[n:pos])
n = i + 1 n = i + 1
escapeRune(writer, rune(v)) escapeRune(writer, rune(v))
@@ -783,6 +819,7 @@ var bPng = []byte("png;")
var bGif = []byte("gif;") var bGif = []byte("gif;")
var bJpeg = []byte("jpeg;") var bJpeg = []byte("jpeg;")
var bWebp = []byte("webp;") var bWebp = []byte("webp;")
var bSvg = []byte("svg;")
var bJs = []byte("javascript:") var bJs = []byte("javascript:")
var bVb = []byte("vbscript:") var bVb = []byte("vbscript:")
var bFile = []byte("file:") var bFile = []byte("file:")
@@ -794,7 +831,8 @@ func IsDangerousURL(url []byte) bool {
if bytes.HasPrefix(url, bDataImage) && len(url) >= 11 { if bytes.HasPrefix(url, bDataImage) && len(url) >= 11 {
v := url[11:] v := url[11:]
if bytes.HasPrefix(v, bPng) || bytes.HasPrefix(v, bGif) || if bytes.HasPrefix(v, bPng) || bytes.HasPrefix(v, bGif) ||
bytes.HasPrefix(v, bJpeg) || bytes.HasPrefix(v, bWebp) {
bytes.HasPrefix(v, bJpeg) || bytes.HasPrefix(v, bWebp) ||
bytes.HasPrefix(v, bSvg) {
return false return false
} }
return true return true


+ 110
- 0
vendor/github.com/yuin/goldmark/text/reader.go View File

@@ -70,6 +70,28 @@ type Reader interface {


// Match performs regular expression searching to current line. // Match performs regular expression searching to current line.
FindSubMatch(reg *regexp.Regexp) [][]byte FindSubMatch(reg *regexp.Regexp) [][]byte

// FindClosure finds corresponding closure.
FindClosure(opener, closer byte, options FindClosureOptions) (*Segments, bool)
}

// FindClosureOptions is options for Reader.FindClosure
type FindClosureOptions struct {
// CodeSpan is a flag for the FindClosure. If this is set to true,
// FindClosure ignores closers in codespans.
CodeSpan bool

// Nesting is a flag for the FindClosure. If this is set to true,
// FindClosure allows nesting.
Nesting bool

// Newline is a flag for the FindClosure. If this is set to true,
// FindClosure searches for a closer over multiple lines.
Newline bool

// Advance is a flag for the FindClosure. If this is set to true,
// FindClosure advances pointers when closer is found.
Advance bool
} }


type reader struct { type reader struct {
@@ -92,6 +114,10 @@ func NewReader(source []byte) Reader {
return r return r
} }


func (r *reader) FindClosure(opener, closer byte, options FindClosureOptions) (*Segments, bool) {
return findClosureReader(r, opener, closer, options)
}

func (r *reader) ResetPosition() { func (r *reader) ResetPosition() {
r.line = -1 r.line = -1
r.head = 0 r.head = 0
@@ -272,6 +298,10 @@ func NewBlockReader(source []byte, segments *Segments) BlockReader {
return r return r
} }


func (r *blockReader) FindClosure(opener, closer byte, options FindClosureOptions) (*Segments, bool) {
return findClosureReader(r, opener, closer, options)
}

func (r *blockReader) ResetPosition() { func (r *blockReader) ResetPosition() {
r.line = -1 r.line = -1
r.head = 0 r.head = 0
@@ -541,3 +571,83 @@ func readRuneReader(r Reader) (rune, int, error) {
r.Advance(size) r.Advance(size)
return rn, size, nil return rn, size, nil
} }

func findClosureReader(r Reader, opener, closer byte, opts FindClosureOptions) (*Segments, bool) {
opened := 1
codeSpanOpener := 0
closed := false
orgline, orgpos := r.Position()
var ret *Segments

for {
bs, seg := r.PeekLine()
if bs == nil {
goto end
}
i := 0
for i < len(bs) {
c := bs[i]
if opts.CodeSpan && codeSpanOpener != 0 && c == '`' {
codeSpanCloser := 0
for ; i < len(bs); i++ {
if bs[i] == '`' {
codeSpanCloser++
} else {
i--
break
}
}
if codeSpanCloser == codeSpanOpener {
codeSpanOpener = 0
}
} else if codeSpanOpener == 0 && c == '\\' && i < len(bs)-1 && util.IsPunct(bs[i+1]) {
i += 2
continue
} else if opts.CodeSpan && codeSpanOpener == 0 && c == '`' {
for ; i < len(bs); i++ {
if bs[i] == '`' {
codeSpanOpener++
} else {
i--
break
}
}
} else if (opts.CodeSpan && codeSpanOpener == 0) || !opts.CodeSpan {
if c == closer {
opened--
if opened == 0 {
if ret == nil {
ret = NewSegments()
}
ret.Append(seg.WithStop(seg.Start + i))
r.Advance(i + 1)
closed = true
goto end
}
} else if c == opener {
if !opts.Nesting {
goto end
}
opened++
}
}
i++
}
if !opts.Newline {
goto end
}
r.AdvanceLine()
if ret == nil {
ret = NewSegments()
}
ret.Append(seg)
}
end:
if !opts.Advance {
r.SetPosition(orgline, orgpos)
}
if closed {
return ret, true
}
return nil, false
}

+ 1531
- 1488
vendor/github.com/yuin/goldmark/util/unicode_case_folding.go
File diff suppressed because it is too large
View File


+ 104
- 67
vendor/github.com/yuin/goldmark/util/util.go View File

@@ -8,6 +8,7 @@ import (
"regexp" "regexp"
"sort" "sort"
"strconv" "strconv"
"unicode"
"unicode/utf8" "unicode/utf8"
) )


@@ -27,6 +28,7 @@ func NewCopyOnWriteBuffer(buffer []byte) CopyOnWriteBuffer {
} }


// Write writes given bytes to the buffer. // Write writes given bytes to the buffer.
// Write allocate new buffer and clears it at the first time.
func (b *CopyOnWriteBuffer) Write(value []byte) { func (b *CopyOnWriteBuffer) Write(value []byte) {
if !b.copied { if !b.copied {
b.buffer = make([]byte, 0, len(b.buffer)+20) b.buffer = make([]byte, 0, len(b.buffer)+20)
@@ -35,7 +37,32 @@ func (b *CopyOnWriteBuffer) Write(value []byte) {
b.buffer = append(b.buffer, value...) b.buffer = append(b.buffer, value...)
} }


// WriteString writes given string to the buffer.
// WriteString allocate new buffer and clears it at the first time.
func (b *CopyOnWriteBuffer) WriteString(value string) {
b.Write(StringToReadOnlyBytes(value))
}

// Append appends given bytes to the buffer.
// Append copy buffer at the first time.
func (b *CopyOnWriteBuffer) Append(value []byte) {
if !b.copied {
tmp := make([]byte, len(b.buffer), len(b.buffer)+20)
copy(tmp, b.buffer)
b.buffer = tmp
b.copied = true
}
b.buffer = append(b.buffer, value...)
}

// AppendString appends given string to the buffer.
// AppendString copy buffer at the first time.
func (b *CopyOnWriteBuffer) AppendString(value string) {
b.Append(StringToReadOnlyBytes(value))
}

// WriteByte writes the given byte to the buffer. // WriteByte writes the given byte to the buffer.
// WriteByte allocate new buffer and clears it at the first time.
func (b *CopyOnWriteBuffer) WriteByte(c byte) { func (b *CopyOnWriteBuffer) WriteByte(c byte) {
if !b.copied { if !b.copied {
b.buffer = make([]byte, 0, len(b.buffer)+20) b.buffer = make([]byte, 0, len(b.buffer)+20)
@@ -44,6 +71,18 @@ func (b *CopyOnWriteBuffer) WriteByte(c byte) {
b.buffer = append(b.buffer, c) b.buffer = append(b.buffer, c)
} }


// AppendByte appends given bytes to the buffer.
// AppendByte copy buffer at the first time.
func (b *CopyOnWriteBuffer) AppendByte(c byte) {
if !b.copied {
tmp := make([]byte, len(b.buffer), len(b.buffer)+20)
copy(tmp, b.buffer)
b.buffer = tmp
b.copied = true
}
b.buffer = append(b.buffer, c)
}

// Bytes returns bytes of this buffer. // Bytes returns bytes of this buffer.
func (b *CopyOnWriteBuffer) Bytes() []byte { func (b *CopyOnWriteBuffer) Bytes() []byte {
return b.buffer return b.buffer
@@ -91,6 +130,9 @@ func VisualizeSpaces(bs []byte) []byte {
bs = bytes.Replace(bs, []byte("\t"), []byte("[TAB]"), -1) bs = bytes.Replace(bs, []byte("\t"), []byte("[TAB]"), -1)
bs = bytes.Replace(bs, []byte("\n"), []byte("[NEWLINE]\n"), -1) bs = bytes.Replace(bs, []byte("\n"), []byte("[NEWLINE]\n"), -1)
bs = bytes.Replace(bs, []byte("\r"), []byte("[CR]"), -1) bs = bytes.Replace(bs, []byte("\r"), []byte("[CR]"), -1)
bs = bytes.Replace(bs, []byte("\v"), []byte("[VTAB]"), -1)
bs = bytes.Replace(bs, []byte("\x00"), []byte("[NUL]"), -1)
bs = bytes.Replace(bs, []byte("\ufffd"), []byte("[U+FFFD]"), -1)
return bs return bs
} }


@@ -110,30 +152,7 @@ func TabWidth(currentPos int) int {
// width=2 is in the tab character. In this case, IndentPosition returns // width=2 is in the tab character. In this case, IndentPosition returns
// (pos=1, padding=2) // (pos=1, padding=2)
func IndentPosition(bs []byte, currentPos, width int) (pos, padding int) { func IndentPosition(bs []byte, currentPos, width int) (pos, padding int) {
if width == 0 {
return 0, 0
}
w := 0
l := len(bs)
i := 0
hasTab := false
for ; i < l; i++ {
if bs[i] == '\t' {
w += TabWidth(currentPos + w)
hasTab = true
} else if bs[i] == ' ' {
w++
} else {
break
}
}
if w >= width {
if !hasTab {
return width, 0
}
return i, w - width
}
return -1, -1
return IndentPositionPadding(bs, currentPos, 0, width)
} }


// IndentPositionPadding searches an indent position with the given width for the given line. // IndentPositionPadding searches an indent position with the given width for the given line.
@@ -147,9 +166,9 @@ func IndentPositionPadding(bs []byte, currentPos, paddingv, width int) (pos, pad
i := 0 i := 0
l := len(bs) l := len(bs)
for ; i < l; i++ { for ; i < l; i++ {
if bs[i] == '\t' {
if bs[i] == '\t' && w < width {
w += TabWidth(currentPos + w) w += TabWidth(currentPos + w)
} else if bs[i] == ' ' {
} else if bs[i] == ' ' && w < width {
w++ w++
} else { } else {
break break
@@ -162,52 +181,56 @@ func IndentPositionPadding(bs []byte, currentPos, paddingv, width int) (pos, pad
} }


// DedentPosition dedents lines by the given width. // DedentPosition dedents lines by the given width.
//
// Deprecated: This function has bugs. Use util.IndentPositionPadding and util.FirstNonSpacePosition.
func DedentPosition(bs []byte, currentPos, width int) (pos, padding int) { func DedentPosition(bs []byte, currentPos, width int) (pos, padding int) {
if width == 0 {
return 0, 0
}
w := 0
l := len(bs)
i := 0
for ; i < l; i++ {
if bs[i] == '\t' {
w += TabWidth(currentPos + w)
} else if bs[i] == ' ' {
w++
} else {
break
}
}
if w >= width {
return i, w - width
}
return i, 0
if width == 0 {
return 0, 0
}
w := 0
l := len(bs)
i := 0
for ; i < l; i++ {
if bs[i] == '\t' {
w += TabWidth(currentPos + w)
} else if bs[i] == ' ' {
w++
} else {
break
}
}
if w >= width {
return i, w - width
}
return i, 0
} }


// DedentPositionPadding dedents lines by the given width. // DedentPositionPadding dedents lines by the given width.
// This function is mostly same as DedentPosition except this function // This function is mostly same as DedentPosition except this function
// takes account into additional paddings. // takes account into additional paddings.
//
// Deprecated: This function has bugs. Use util.IndentPositionPadding and util.FirstNonSpacePosition.
func DedentPositionPadding(bs []byte, currentPos, paddingv, width int) (pos, padding int) { func DedentPositionPadding(bs []byte, currentPos, paddingv, width int) (pos, padding int) {
if width == 0 {
return 0, paddingv
}
w := 0
i := 0
l := len(bs)
for ; i < l; i++ {
if bs[i] == '\t' {
w += TabWidth(currentPos + w)
} else if bs[i] == ' ' {
w++
} else {
break
}
}
if w >= width {
return i - paddingv, w - width
}
return i - paddingv, 0
if width == 0 {
return 0, paddingv
}
w := 0
i := 0
l := len(bs)
for ; i < l; i++ {
if bs[i] == '\t' {
w += TabWidth(currentPos + w)
} else if bs[i] == ' ' {
w++
} else {
break
}
}
if w >= width {
return i - paddingv, w - width
}
return i - paddingv, 0
} }


// IndentWidth calculate an indent width for the given line. // IndentWidth calculate an indent width for the given line.
@@ -249,6 +272,10 @@ func FirstNonSpacePosition(bs []byte) int {
// If codeSpan is set true, it ignores characters in code spans. // If codeSpan is set true, it ignores characters in code spans.
// If allowNesting is set true, closures correspond to nested opener will be // If allowNesting is set true, closures correspond to nested opener will be
// ignored. // ignored.
//
// Deprecated: This function can not handle newlines. Many elements
// can be existed over multiple lines(e.g. link labels).
// Use text.Reader.FindClosure.
func FindClosure(bs []byte, opener, closure byte, codeSpan, allowNesting bool) int { func FindClosure(bs []byte, opener, closure byte, codeSpan, allowNesting bool) int {
i := 0 i := 0
opened := 1 opened := 1
@@ -668,7 +695,7 @@ func URLEscape(v []byte, resolveReference bool) []byte {
n = i n = i
continue continue
} }
if int(u8len) >= len(v) {
if int(u8len) > len(v) {
u8len = int8(len(v) - 1) u8len = int8(len(v) - 1)
} }
if u8len == 0 { if u8len == 0 {
@@ -754,7 +781,7 @@ func FindEmailIndex(b []byte) int {


var spaces = []byte(" \t\n\x0b\x0c\x0d") var spaces = []byte(" \t\n\x0b\x0c\x0d")


var spaceTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
var spaceTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}


var punctTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} var punctTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}


@@ -777,11 +804,21 @@ func IsPunct(c byte) bool {
return punctTable[c] == 1 return punctTable[c] == 1
} }


// IsPunctRune returns true if the given rune is a punctuation, otherwise false.
func IsPunctRune(r rune) bool {
return int32(r) <= 256 && IsPunct(byte(r)) || unicode.IsPunct(r)
}

// IsSpace returns true if the given character is a space, otherwise false. // IsSpace returns true if the given character is a space, otherwise false.
func IsSpace(c byte) bool { func IsSpace(c byte) bool {
return spaceTable[c] == 1 return spaceTable[c] == 1
} }


// IsSpaceRune returns true if the given rune is a space, otherwise false.
func IsSpaceRune(r rune) bool {
return int32(r) <= 256 && IsSpace(byte(r)) || unicode.IsSpace(r)
}

// IsNumeric returns true if the given character is a numeric, otherwise false. // IsNumeric returns true if the given character is a numeric, otherwise false.
func IsNumeric(c byte) bool { func IsNumeric(c byte) bool {
return c >= '0' && c <= '9' return c >= '0' && c <= '9'


+ 6
- 3
vendor/github.com/yuin/goldmark/util/util_unsafe.go View File

@@ -13,8 +13,11 @@ func BytesToReadOnlyString(b []byte) string {
} }


// StringToReadOnlyBytes returns bytes converted from given string. // StringToReadOnlyBytes returns bytes converted from given string.
func StringToReadOnlyBytes(s string) []byte {
func StringToReadOnlyBytes(s string) (bs []byte) {
sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh := reflect.SliceHeader{Data: sh.Data, Len: sh.Len, Cap: sh.Len}
return *(*[]byte)(unsafe.Pointer(&bh))
bh := (*reflect.SliceHeader)(unsafe.Pointer(&bs))
bh.Data = sh.Data
bh.Cap = sh.Len
bh.Len = sh.Len
return
} }

+ 1
- 1
vendor/modules.txt View File

@@ -853,7 +853,7 @@ github.com/xdg/stringprep
# github.com/yohcop/openid-go v1.0.0 # github.com/yohcop/openid-go v1.0.0
## explicit ## explicit
github.com/yohcop/openid-go github.com/yohcop/openid-go
# github.com/yuin/goldmark v1.1.30
# github.com/yuin/goldmark v1.4.13
## explicit ## explicit
github.com/yuin/goldmark github.com/yuin/goldmark
github.com/yuin/goldmark/ast github.com/yuin/goldmark/ast


Loading…
Cancel
Save