Reputation: 163
I'm writing a website using clojure hiccup for html and clojure garden for css. Now I'm having trouble using clojure.string/split to split a path for the filename. So for some/path/file.md
I want file
.
For context here's the code snippet, where the problem is
(defn blog-card [post]
[:a.blog-post-card-link {:href (->> (:location post)
(string/split #"/")
last
(string/replace ".md" ""))}
[:div.blog-post-card
-- rest of content -- ]])
now this function gets called here
(defn blog-content []
[:div.blog-content
[:div.blog-post-cards-list
(let [json-str (slurp "resources/public/markdown/posts/all-posts.json")
posts (json/parse-string json-str true)]
(map blog-card (reverse posts)))]])
where json
is [cheshire.core :as json]
and string
is [clojure.string :as string]
. The all-posts.json
looks like this
[
{
"id": "0",
"title": "Test article #1",
"description": "This article is just a place-filler for development.",
"image": "images/mountain.jpg",
"date": "10 Oct 2023",
"author": "Elías Hauksson",
"location": "markdown/posts/test-post01.md"
},
-- more test entries --
]
So with this example, what I'd like to achieve is, that when the card is pressed, one gets redirected to /test-post01
Now when I try to run this code I get the following error message
HTTP ERROR 500 java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.regex.Pattern (java.lang.String and java.util.regex.Pattern are in module java.base of loader 'bootstrap')
URI: /blog
STATUS: 500
MESSAGE: java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.regex.Pattern (java.lang.String and java.util.regex.Pattern are in module java.base of loader 'bootstrap')
SERVLET: -
CAUSED BY: java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.regex.Pattern (java.lang.String and java.util.regex.Pattern are in module java.base of loader 'bootstrap')
Caused by:
java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.regex.Pattern (java.lang.String and java.util.regex.Pattern are in module java.base of loader 'bootstrap')
at clojure.string$split.invokeStatic(string.clj:219)
at clojure.string$split.invoke(string.clj:219)
at eliashaukssoncom.views.blog$blog_card.invokeStatic(blog.clj:10)
at eliashaukssoncom.views.blog$blog_card.invoke(blog.clj:8)
at clojure.core$map$fn__5935.invoke(core.clj:2772)
at clojure.lang.LazySeq.sval(LazySeq.java:42)
at clojure.lang.LazySeq.seq(LazySeq.java:51)
at clojure.lang.RT.seq(RT.java:535)
at clojure.core$seq__5467.invokeStatic(core.clj:139)
at clojure.core$map$fn__5935.invoke(core.clj:2763)
at clojure.lang.LazySeq.sval(LazySeq.java:42)
at clojure.lang.LazySeq.seq(LazySeq.java:51)
at clojure.lang.RT.seq(RT.java:535)
at clojure.core$seq__5467.invokeStatic(core.clj:139)
at clojure.core$apply.invokeStatic(core.clj:662)
at clojure.core$apply.invoke(core.clj:662)
at hiccup.compiler$fn__3080.invokeStatic(compiler.clj:139)
at hiccup.compiler$fn__3080.invoke(compiler.clj:133)
at hiccup.compiler$fn__3063$G__3058__3068.invoke(compiler.clj:119)
at clojure.core$map$fn__5935.invoke(core.clj:2770)
at clojure.lang.LazySeq.sval(LazySeq.java:42)
at clojure.lang.LazySeq.seq(LazySeq.java:51)
at clojure.lang.RT.seq(RT.java:535)
at clojure.core$seq__5467.invokeStatic(core.clj:139)
at clojure.core$apply.invokeStatic(core.clj:662)
at clojure.core$apply.invoke(core.clj:662)
at hiccup.compiler$fn__3080.invokeStatic(compiler.clj:139)
at hiccup.compiler$fn__3080.invoke(compiler.clj:133)
at hiccup.compiler$fn__3063$G__3058__3068.invoke(compiler.clj:119)
at hiccup.compiler$render_element.invokeStatic(compiler.clj:129)
at hiccup.compiler$render_element.invoke(compiler.clj:123)
at hiccup.compiler$fn__3078.invokeStatic(compiler.clj:136)
at hiccup.compiler$fn__3078.invoke(compiler.clj:133)
at hiccup.compiler$fn__3063$G__3058__3068.invoke(compiler.clj:119)
at clojure.core$map$fn__5935.invoke(core.clj:2770)
at clojure.lang.LazySeq.sval(LazySeq.java:42)
at clojure.lang.LazySeq.seq(LazySeq.java:51)
at clojure.lang.RT.seq(RT.java:535)
at clojure.core$seq__5467.invokeStatic(core.clj:139)
at clojure.core$apply.invokeStatic(core.clj:662)
at clojure.core$apply.invoke(core.clj:662)
at hiccup.compiler$fn__3080.invokeStatic(compiler.clj:139)
at hiccup.compiler$fn__3080.invoke(compiler.clj:133)
at hiccup.compiler$fn__3063$G__3058__3068.invoke(compiler.clj:119)
at hiccup.compiler$render_element.invokeStatic(compiler.clj:129)
at hiccup.compiler$render_element.invoke(compiler.clj:123)
at hiccup.compiler$fn__3078.invokeStatic(compiler.clj:136)
at hiccup.compiler$fn__3078.invoke(compiler.clj:133)
at hiccup.compiler$fn__3063$G__3058__3068.invoke(compiler.clj:119)
at eliashaukssoncom.views.base$base$fn__1001.invoke(base.clj:26)
at eliashaukssoncom.views.base$base.invokeStatic(base.clj:26)
at eliashaukssoncom.views.base$base.invoke(base.clj:25)
at eliashaukssoncom.views.blog$blog_page.invokeStatic(blog.clj:35)
at eliashaukssoncom.views.blog$blog_page.invoke(blog.clj:34)
at eliashaukssoncom.handler$fn__1026.invokeStatic(handler.clj:13)
at eliashaukssoncom.handler$fn__1026.invoke(handler.clj:13)
at compojure.core$wrap_response$fn__2214.invoke(core.clj:158)
at compojure.core$wrap_route_middleware$fn__2198.invoke(core.clj:128)
at compojure.core$wrap_route_info$fn__2203.invoke(core.clj:137)
at compojure.core$wrap_route_matches$fn__2207.invoke(core.clj:146)
at compojure.core$routing$fn__2222.invoke(core.clj:185)
at clojure.core$some.invokeStatic(core.clj:2718)
at clojure.core$some.invoke(core.clj:2709)
at compojure.core$routing.invokeStatic(core.clj:185)
at compojure.core$routing.doInvoke(core.clj:182)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invokeStatic(core.clj:669)
at clojure.core$apply.invoke(core.clj:662)
at compojure.core$routes$fn__2226.invoke(core.clj:192)
at ring.middleware.resource$wrap_resource_prefer_resources$fn__596.invoke(resource.clj:25)
at clojure.lang.Var.invoke(Var.java:384)
at ring.adapter.jetty$proxy_handler$fn__420.invoke(jetty.clj:27)
at ring.adapter.jetty.proxy$org.eclipse.jetty.server.handler.AbstractHandler$ff19274a.handle(Unknown Source)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
at org.eclipse.jetty.server.Server.handle(Server.java:516)
at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:487)
at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:732)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:479)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:277)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034)
at java.base/java.lang.Thread.run(Thread.java:829)
(:location post)
with "some/test/path/file.md"
->>
operator. Like this{:href (string/replace (last (string/split (:location post) #"/")) #".md" "")}
Edit: Point 2 turns out to be the correct solution (as mentioned by @Eugene_Pakhomov), it was a typo in my code that didn't make it work the first time.
Thank you in advance for your suggestions.
Upvotes: 0
Views: 111
Reputation: 29984
For work with filesystem names & paths, it is always best to use the built-in functionality of the java.io
package. In this case, the File
class can safely & easily parse out pathname segments regardless of operating system.
I would solve it like so:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test)
(:require
[clojure.string :as str])
(:import
[java.io File]))
(verify
(let [file-obj (File. "some/path/file.md")
name-str (.getName file-obj)
name-root (first (str/split name-str #"\."))]
(is= "file.md" name-str)
(is= "file" name-root)))
Note that (first (str/split ...))
assumes there is only a single .
("dot") character in the file name! For robust code, please add error checking!
Upvotes: 1
Reputation: 4482
As an alternative, you could also do this directly with regex and for example re-find
:
(let [post {:location "/some/path/here/foobar.md"}]
(re-find #"([^/.]+).md" (:location post)))
=> ["foobar.md" "foobar"]
Where the regex says:
(
- open paren, start a capturing group in regex[^/.]+
- match any character except (slash /
or period .
) one or more times (+
).)
- close capturing group.md
- expect the captured group to be followed by .md
the returned vector contains the full match "foobar.md"
followed by the captured group "foobar"
which is what you are after.
If you want to use the threading macros (->>
) this could look as follows:
(let [post {:location "/asd/adsf/asdf/foobar.md"}]
(->> (:location post)
(re-find #"([^/.]+).md" )
second))
=> "foobar"
Or if you want to go even more concise, you can use regex lookaround assertions:
(re-find #"[^/.]+(?=\.md)" "/asd/adsf/asdf/foobar.md")
=> "foobar"
where the (?=...)
part is a "zero-width positive lookahead". This essentially means "match a string consisting of anything except / or . followed by the string ".md" but do not make ".md" part of the match". Documentation on the regex implementation in clojure can be found on the java pattern class javadocs as clojure uses the JVM pattern implementation.
Should be noted that as for your initial example, if the path does not end with .md
, the match will just return nil
and not match the "file name" part. If you need arbitrary file extensions, you can do something like:
(re-find #"[^/.]+(?=\..+)" "/asd/adsf/asdf/foobar.orange")
=> "foobar"
Upvotes: 1
Reputation: 10727
clojure.string/split
accepts a string as the first argument and a regular expression as the second. The code in the "What I've tried" section should work. The code in the main section doesn't work because (->> (:location post) (string/split #"/"))
is expanded into (string/split #"/" (:location post))
.
Upvotes: 4