Repository: gohugoio/hugo (language: go)

Issue:
Double-hyphens in post URLs reduce to single-hyphens; regression introduced in v0.50 causes links to break
<!-- Please answer these questions before submitting your issue. Thanks! -->

### What version of Hugo are you using (`hugo version`)?

<pre>
$ hugo version
Hugo Static Site Generator v0.70.0-7F47B99E linux/amd64 BuildDate: 2020-05-06T11:18:50Z
</pre>

### Does this issue reproduce with the latest release?

Yes

---

**Expected behavior**: If the content file is named `content/foo--bar.md`, the page URL is expected to be `<site>/foo--bar/`.

**Observed behavior**: If the content file is named `content/foo--bar.md`, the page URL becomes `<site>/foo-bar/` (note that the double-hyphen becomes single-hyphen).

I believe this regression was caused ~~after the switch to Goldmark.~~ in https://github.com/gohugoio/hugo/commit/e421696d.


### Recipe to reproduce this issue

1. `git clone https://gitlab.com/hugo-mwe/double-hyphens-in-post-urls` (Content file: https://gitlab.com/hugo-mwe/double-hyphens-in-post-urls/-/blob/master/content/post--with--double--hyphens.md )
2. `cd double-hyphens-in-post-urls`
3. hugo

Above demo site is also deployed at https://determined-leakey-137470.netlify.app/ .

#### Observed output

In `public/index.html`, we will see:

```html
    <body lang="en">
        
        
            <p>Page link = /post-with-double-hyphens/</p>
```

#### Expected output

```html
    <body lang="en">
        
        
            <p>Page link = /post--with--double--hyphens/</p>
```

### Same issue reported by other users

- https://discourse.gohugo.io/t/force-hyphens-in-url/23958

### Seriousness of this regression

I have quite a few pages with double-hyphens.. Double hyphens are used as idea separator in URLs. Here are few examples:

```
posts/deeply-nested-org-todo-headings--h1.md
posts/deeply-nested-org-todo-headings--h2.md
posts/deeply-nested-org-todo-headings--h5.md
posts/deeply-nested-org-todo-headings--h6.md
posts/deeply-nested-org-todo-headings--default-h.md
posts/filling-not-preserved-for-chinese-characters--preserve-filling-off.md
posts/filling-not-preserved-for-chinese-characters--preserve-filling-on.md
```

I link to these pages for references, and now they break because now double-hyphens are auto-reduced to single-hyphens. 

Can the number of hyphens specified by the user in the content file name be preserved?



Discussion / hints:
>I believe this regression was caused after the switch to Goldmark.

No, I think this is how it has been behaving for several years.
Indeed, this change happened about 1.5 years back in https://github.com/gohugoio/hugo/releases/tag/v0.50 .

Sincere request to allow users to retain their hyphens where they need them.

I only landed up on this today as I was tracing through one of the old ox-hugo tests linked in old issues.
Making the best guess from the changelog.. ~~probably this caused it?: https://github.com/gohugoio/hugo/commit/fae48d7457de96969ec53349194dcbfa45adc269 /cc @moorereason~~ 

**Update**: It's actually this: https://github.com/gohugoio/hugo/commit/e421696d 

> - Move the "double hyphen and space" logic into UnicodeSanitize

> The last bullet may be slightly breaking for some that now does not get the "--" in some URLs, but we need to reduce the amount of URL logic.
@bep This can be made a non-breaking fix if the double-hyphens are retained at least when the user has explicitly set the permalinks option to use the `:filename`. Example:

```toml
[permalinks]
  "/" = "/:filename/"
```

Right now, even with this option set, when the file name is `post--with--double--hyphens.md`, the permalink is `/post-with-double-hyphens/`. Can the filename faithfulness be retained with the `:filename` used for permalinks option?

Thanks.
This issue has been automatically marked as stale because it has not had recent activity. The resources of the Hugo team are limited, and so we are asking for your help.
If this is a **bug** and you can still reproduce this error on the <code>master</code> branch, please reply with all of the information you have about it in order to keep the issue open.
If this is a **feature request**, and you feel that it is still relevant and valuable, please tell us why.
This issue will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.

@bep Can you please look at this issue? This one issue is still bugging me. 

Fix patch:
diff --git a/helpers/path.go b/helpers/path.go
index a40e0f8ff37..b302b156961 100644
--- a/helpers/path.go
+++ b/helpers/path.go
@@ -87,7 +87,8 @@ func ishex(c rune) bool {
 // a predefined set of special Unicode characters.
 // If RemovePathAccents configuration flag is enabled, Unicode accents
 // are also removed.
-// Spaces will be replaced with a single hyphen, and sequential hyphens will be reduced to one.
+// Hyphens in the original input are maintained.
+// Spaces will be replaced with a single hyphen, and sequential replacement hyphens will be reduced to one.
 func (p *PathSpec) UnicodeSanitize(s string) string {
 	if p.RemovePathAccents {
 		s = text.RemoveAccentsString(s)
@@ -95,20 +96,30 @@ func (p *PathSpec) UnicodeSanitize(s string) string {
 
 	source := []rune(s)
 	target := make([]rune, 0, len(source))
-	var prependHyphen bool
+	var (
+		prependHyphen bool
+		wasHyphen     bool
+	)
 
 	for i, r := range source {
-		isAllowed := r == '.' || r == '/' || r == '\\' || r == '_' || r == '#' || r == '+' || r == '~'
+		isAllowed := r == '.' || r == '/' || r == '\\' || r == '_' || r == '#' || r == '+' || r == '~' || r == '-'
 		isAllowed = isAllowed || unicode.IsLetter(r) || unicode.IsDigit(r) || unicode.IsMark(r)
 		isAllowed = isAllowed || (r == '%' && i+2 < len(source) && ishex(source[i+1]) && ishex(source[i+2]))
 
 		if isAllowed {
+			// track explicit hyphen in input; no need to add a new hyphen if
+			// we just saw one.
+			wasHyphen = r == '-'
+
 			if prependHyphen {
-				target = append(target, '-')
+				// if currently have a hyphen, don't prepend an extra one
+				if !wasHyphen {
+					target = append(target, '-')
+				}
 				prependHyphen = false
 			}
 			target = append(target, r)
-		} else if len(target) > 0 && (r == '-' || unicode.IsSpace(r)) {
+		} else if len(target) > 0 && !wasHyphen && unicode.IsSpace(r) {
 			prependHyphen = true
 		}
 	}


Test patch:
diff --git a/helpers/path_test.go b/helpers/path_test.go
index 1d2dc118431..6a119a7412c 100644
--- a/helpers/path_test.go
+++ b/helpers/path_test.go
@@ -40,6 +40,10 @@ func TestMakePath(t *testing.T) {
 		expected      string
 		removeAccents bool
 	}{
+		{"dot.slash/backslash\\underscore_pound#plus+hyphen-", "dot.slash/backslash\\underscore_pound#plus+hyphen-", true},
+		{"abcXYZ0123456789", "abcXYZ0123456789", true},
+		{"%20 %2", "%20-2", true},
+		{"foo- bar", "foo-bar", true},
 		{"  Foo bar  ", "Foo-bar", true},
 		{"Foo.Bar/foo_Bar-Foo", "Foo.Bar/foo_Bar-Foo", true},
 		{"fOO,bar:foobAR", "fOObarfoobAR", true},
@@ -52,7 +56,7 @@ func TestMakePath(t *testing.T) {
 		{"a%C3%B1ame", "a%C3%B1ame", false},         // Issue #1292
 		{"this+is+a+test", "this+is+a+test", false}, // Issue #1290
 		{"~foo", "~foo", false},                     // Issue #2177
-
+		{"foo--bar", "foo--bar", true},              // Issue #7288
 	}
 
 	for _, test := range tests {