3

I'm playing with Turtle and I'm faced with the following problem.

I want to do something like (in shell)

ls | grep 'foo'

My attempt using Turtle is

grep (prefix "foo") (ls ".") & view

But I got the following message

Couldn't match type ‘Turtle.FilePath’ with ‘Text’
Expected type: Shell Text
  Actual type: Shell Turtle.FilePath
In the second argument of ‘grep’, namely ‘(ls ".")’
In the first argument of ‘(&)’, namely
  ‘grep (prefix "foo") (ls ".")’

I understand ls returns FilePath whereas grep works on Text, so what can I do ?

Update

There are obviously solutions which involves converting back and forth from FilePath to Text. That's beyond the simplicity I would expect shell-like program.

Someone mentioned the find function, which somehow could solves the problem. However find is the equivalent to the find shell function and I was trying just to do ls | grep "foo". I'm not trying to solve a real life problem (if I were, I would switch to bash instead) but trying to combine simple bricks as I would do in bash. Unfortunately, it doesn't seem that bricks in Turtle are that easy to combine :-(.

mb14
  • 22,276
  • 7
  • 60
  • 102

4 Answers4

1

Instead of grep, we can use match, in combination with the MonadPlus instance of Shell for filtering:

filterByPattern :: MonadPlus m => Pattern x -> FilePath -> m FilePath
filterByPattern somepattern somepath =
    case match somepattern (either id id (toText somepath)) of
        []        -> mzero
        otherwise -> return somepath

greppedls :: FilePath -> Pattern x -> Shell FilePath
greppedls somepath somepattern = 
   ls somepath >>= filterByPattern somepattern

Edit: Instead of using the unnecesarily general MonadPlus, here's an implementation that filters using the turtle-specific combinator select:

filterByPattern :: Pattern x -> FilePath -> Shell FilePath
filterByPattern somepattern somepath =
    case match somepattern (either id id (toText somepath)) of
        []        -> select []         -- no matches, so filter this path  
        otherwise -> select [somepath] -- let this path pass

A value foo :: Shell a is a bit like a "list of as". If we have a function genlist :: a -> Shell b that for each a generates a (perhaps empty!) list of bs, we can obtain a list of bs using the (>>=) operator: foo >>= genlist.

Edit#2: The standard turtle function find already filters files using a pattern. It is recursive and searches in subdirectories.

danidiaz
  • 26,936
  • 4
  • 45
  • 95
  • Your code might compiles but doesn't work. `repr (Filepath "foo")` doesn't return `"foo"` but `"Filepath \"foo\"` which means `prefix "foo"` won't match `foo` files. Also `Turtle` claims to be a better bash. This bit of code is much too complicated compared to is bash equivalent `ls | grep "foo"`. – mb14 Aug 29 '15 at 12:11
  • @mb14 I have removed the `repr` issue by using [`toText`](http://hackage.haskell.org/package/system-filepath-0.4.13.4/docs/Filesystem-Path-CurrentOS.html#v:toText) instead. – danidiaz Aug 29 '15 at 12:19
  • @mb14 As for the overcomplication, perhaps something like `filterByPattern` should be in the library (or perhaps it already is, and I haven't seen it). Once such a function exists, `ls "." >>= filterByPattern "foo"` is not much worse than `ls | grep "foo"`. – danidiaz Aug 29 '15 at 12:25
  • I kind of agree, however, `grep` already exists and do the same. It probably will be better to just have `type FilePath = Text` as other filepath library do. Or maybe a function which *lift* text functions to filepath functions. – mb14 Aug 29 '15 at 12:56
  • About the complexity, If I'll have to do that for such a simple bash task I'm better using just plain IO, regexp etc and not bother with Turtle. – mb14 Aug 29 '15 at 13:06
  • 1
    @mb14 It appears there already is a standard turtle function that filters paths by pattern. I searched [Hayoo](http://hayoo.fh-wedel.de/) for the type `Pattern x -> FilePath -> Shell FilePath` and [this function](http://hackage.haskell.org/package/turtle-1.2.1/docs/Turtle-Prelude.html#v:find) came up. It searches recursively, however. – danidiaz Aug 29 '15 at 13:16
1

To convert from FilePath into Text you use:

fp :: Format r (FilePath -> r)

Here is an example:

format fp ("usr" </> "lib")

There is a couple of issues about this so Gabriel has decided to update the tutorial a few days ago:

https://github.com/Gabriel439/Haskell-Turtle-Library/commit/a2fff2acf912cc7adb2e02671340822feb0e9172

To answer your (updated) question, the best I can come up is:

format fp <$> ls "." & grep (has "foo") & view

& is playing the role of |.

As a personal note, it is of course not as short as ls | grep 'foo' but still quite elegant given that Haskell is a typed language.

Pierre R
  • 216
  • 1
  • 7
  • As of 2021, the type of `grep` seems to have changed and this solution is not type correct anymore: `format fp <$> ls "."` has type `Shell Text` instead of `Shell Line` as expected by `grep (has "foo")`. This however works: `unsafeTextToLine <$> format fp <$> ls "." & grep (has "ver") & view`. – FK82 Feb 17 '21 at 20:26
1

The literal answer is this one-liner:

example =
    view (ls "." & fmap (format fp) & grep (prefix "foo") & fmap toText)

The idiomatic answer is to use the find utility

Gabriella Gonzalez
  • 34,863
  • 3
  • 77
  • 135
0

Try to use repr

repr :: Show a => a -> Text
Bakuriu
  • 98,325
  • 22
  • 197
  • 231
Luka Rahne
  • 10,336
  • 3
  • 34
  • 56
  • +1 Not bad but that solve half of the problem. Let's suppose I want to move the selected file somewhere, then I need to convert them back to `FilePath`. – mb14 Aug 29 '15 at 10:34
  • 1
    `repr` works in ghci but I can't find it on the link you provided. – mb14 Aug 29 '15 at 11:10
  • @mb14 I've edited the answer to include the correct link. Also note that `repr = format w` where `format` is described [here](https://hackage.haskell.org/package/turtle-1.0.0/docs/Turtle-Format.html#v:format). – Bakuriu Aug 29 '15 at 11:36
  • `repr` is not good then. It's equivalent to show, and add quotes and stuff. – mb14 Aug 29 '15 at 11:44