Repository: Flutter-Bounty-Hunters/static_shock (dart)

Issue:
[Shock] - Pretty URLs aren't working for pages within a directory
A pretty URL is a URL that avoids the use of a specific file, e.g., `mydomain.com/posts/hello-world/` instead of `mydomain.com/posts/hello-world.html` or `mydomain.com/posts/hello-world/index.html`.

Some aspect of the pretty URL system is failing for pages that explicitly declare an index file.

Given the following structure:

```
source/
  posts/
    hello-world/
      index.md
```

The final URL is `mydomain.com/posts/hello-world/index.html` when instead it should be `mydomain.com/posts/hello-world/`.

Pull request description:
Fix: Prettify URLs for pages with explicit index files (Resolves #152)

Consider a file like `source/posts/news/some-event/index.md`. It should map to URL `/posts/news/some-event/` but instead was mapping to `/posts/news/some-event/index.html`.

Patch:
diff --git a/packages/static_shock/lib/src/files.dart b/packages/static_shock/lib/src/files.dart
index 291732a..5f2a7ab 100644
--- a/packages/static_shock/lib/src/files.dart
+++ b/packages/static_shock/lib/src/files.dart
@@ -151,7 +151,8 @@ class FileRelativePath implements RelativePath {
   DirectoryRelativePath get containingDirectory => DirectoryRelativePath(directoryPath);
 
   @override
-  List<String> get directories => directoryPath.split(Platform.pathSeparator).where((item) => item.isNotEmpty).toList();
+  List<String> get directories => directoryPath.split(Platform.pathSeparator).where((item) => item.isNotEmpty).toList()
+    ..removeWhere((dirName) => dirName == "." || dirName == "..");
 
   FileRelativePath copyWith({
     String? directoryPath,
diff --git a/packages/static_shock/lib/src/plugins/pretty_urls.dart b/packages/static_shock/lib/src/plugins/pretty_urls.dart
index dd2c49c..9cf3c57 100644
--- a/packages/static_shock/lib/src/plugins/pretty_urls.dart
+++ b/packages/static_shock/lib/src/plugins/pretty_urls.dart
@@ -24,17 +24,28 @@ class PrettyPathPageTransformer implements PageTransformer {
 
   @override
   FutureOr<void> transformPage(StaticShockPipelineContext context, Page page) async {
-    if (page.destinationPath?.filename == "index") {
-      // The file is already an index file. Nothing to prettify.
+    final originalPath = page.destinationPath ?? page.sourcePath;
+    if (originalPath.directories.isEmpty && originalPath.filename == "index") {
+      // This is the root index file.
+      page.url = "/";
       return;
     }
 
-    final originalPath = page.destinationPath ?? page.sourcePath;
-    page
-      ..url = "${originalPath.directoryPath}${originalPath.filename}${Platform.pathSeparator}"
-      ..destinationPath = originalPath.copyWith(
+    final pathBeforePageFile = originalPath.directories.isNotEmpty ? "/${originalPath.directories.join("/")}/" : "/";
+
+    page.url = originalPath.filename != "index"
+        // First case is like "posts/news/hello-world.md" -> "/posts/news/hello-world/"
+        ? "$pathBeforePageFile${originalPath.filename}/"
+        // Second case is like "posts/news/hello-world/index.md" -> "/posts/news/hello-world/"
+        : pathBeforePageFile;
+
+    if (page.destinationPath?.filename != "index") {
+      // This page's destination hasn't been fully configured yet. Set
+      // destination page to an index file so webservers can serve it.
+      page.destinationPath = originalPath.copyWith(
         directoryPath: "${originalPath.directoryPath}${originalPath.filename}${Platform.pathSeparator}",
         filename: "index",
       );
+    }
   }
 }


Test patch:
diff --git a/packages/static_shock/test/plugins/pretty_urls_test.dart b/packages/static_shock/test/plugins/pretty_urls_test.dart
new file mode 100644
index 0000000..7b37f54
--- /dev/null
+++ b/packages/static_shock/test/plugins/pretty_urls_test.dart
@@ -0,0 +1,59 @@
+import 'dart:io';
+
+import 'package:mason_logger/mason_logger.dart';
+import 'package:static_shock/static_shock.dart';
+import 'package:test/test.dart';
+
+void main() {
+  group("Plugins > pretty URLs >", () {
+    test("pretties URL for page with single source file", () async {
+      final context = StaticShockPipelineContext(Logger(), Directory("/User/Fake/website"));
+      final page = Page(FileRelativePath("./posts/news", "fake", "md"), "");
+      context.pagesIndex.addPage(page);
+
+      PrettyPathPageTransformer().transformPage(context, page);
+
+      expect(page.url, "/posts/news/fake/");
+    });
+
+    test("pretties URL for page with explicit index file", () async {
+      final context = StaticShockPipelineContext(Logger(), Directory("/User/Fake/website"));
+      final page = Page(FileRelativePath("./posts/news/fake", "index", "md"), "");
+      context.pagesIndex.addPage(page);
+
+      PrettyPathPageTransformer().transformPage(context, page);
+
+      expect(page.url, "/posts/news/fake/");
+    });
+
+    test("pretties URL for root-level page", () async {
+      final context = StaticShockPipelineContext(Logger(), Directory("/User/Fake/website"));
+      final page = Page(FileRelativePath("./", "fake", "md"), "");
+      context.pagesIndex.addPage(page);
+
+      PrettyPathPageTransformer().transformPage(context, page);
+
+      expect(page.url, "/fake/");
+    });
+
+    test("keeps root level index HTML file as-is", () async {
+      final context = StaticShockPipelineContext(Logger(), Directory("/User/Fake/website"));
+      final page = Page(FileRelativePath("./", "index", "html"), "");
+      context.pagesIndex.addPage(page);
+
+      PrettyPathPageTransformer().transformPage(context, page);
+
+      expect(page.url, "/");
+    });
+
+    test("keeps root level index Markdown file as-is", () async {
+      final context = StaticShockPipelineContext(Logger(), Directory("/User/Fake/website"));
+      final page = Page(FileRelativePath("./", "index", "md"), "");
+      context.pagesIndex.addPage(page);
+
+      PrettyPathPageTransformer().transformPage(context, page);
+
+      expect(page.url, "/");
+    });
+  });
+}