earnestt1234/seedir

Feature Request: Allow Changing emojis based on levels

Closed this issue Β· 12 comments

Hey,
it would be a nice feature especially for large directories with many files to have more then two emojis.

Normal:
πŸ“ exampledir/
β”œβ”€πŸ“ scrooge/
β”‚ β”œβ”€πŸ“ patrimonial/
β”‚ β”œβ”€πŸ“ sandal/
β”‚ β””β”€πŸ“„ light.pdf
β”œβ”€πŸ“„ jowly.pdf
β””β”€πŸ“„ monkish.txt

Approach:
πŸ“˜ exampledir/
β”œβ”€πŸ“• scrooge/
β”‚ β”œβ”€ πŸ“•patrimonial/
β”‚ β”œβ”€πŸ“• sandal/
β”‚ β””β”€πŸ“„ light.pdf
β”œβ”€πŸ“ jowly.pdf
β””β”€πŸ“ monkish.txt

It would be much easier to read and see which file belongs to which level :)

@timweissenfels This is an interesting idea. Can you suggest what the call/arguments for doing so would look like? Are you expecting say, a new style (or styles) that just picks a few emojis for you? Or the ability to specify the emojis you want to use for each depth (for folders and files)?

@earnestt1234 I'am not that familiar with you code yet, but I would say the first thing would be to get awareness to the tree depth.

Let the user input his own list for example folder_emojis = [πŸ“,πŸ“‚,πŸ—‚πŸ“š], use them in this order for the different levels.
Maybe also add a flag or parameter to decide if the level of depth exceeds the amount of emojis:
Then either use a default emoji or Repeat the list.

Same goes for Files.

I'am not quite sure if this is sufficient for a new style. Mainly because it fits the same usage as the already existing emoji style, it just adds new functionality.
On the other hand, with a new Parameter that you have to set it is maybe not that easy to copy paste from documentation and the first time experience wouldn't be that great... maybe?

What do you think about that?

@timweissenfels Gothcha, yea the list approach makes sense. I had also thought a dictionary might be possible (it's a little more explicit, but more to type). Key is depth and value is emoji:

import seedir as sd
sd.seedir('path', folderstart={0:'πŸ“˜', 1: 'πŸ“•', 2: 'πŸ“•'}, filestart={1:'πŸ“', 2:'πŸ“„'})

When creating the diagram, the depth of the current entry is accessible, so it would just be a matter of getting the correct emoji (either by the index in a list or the value of a dict).

However, I'm starting to think it might be better to collect this feature by adding some more general customizability. My thoughts are:

  1. While you want to change specific symbols used in the emoji style, I think it could apply to others as well. So generally, you would allow for multiple tokens (i.e. extend, split, space, final, as well as folderstart, and filestart) to be used in the same diagram.
  2. In addition to depth, it might be useful to set the emoji (or other symbol) based on the item name, or some other parameter (the extension, the contents of the folder, etc.). E.g. I want all my PDFs to have one emoji, all my images to have another, etc. So that leads me to think that letting the user pass some sort of function might be the way to go (similar to the mask parameter: #1). For each item, you call a user-defined function which returns which emojis (or other symbols) to use.

The tricky part with the second point is that by simply passing a folder/file name, you won't have the depth of that item. So you might need to enable the function to accept the depth (or other parameters) as arguments.

Hopefully this isn't getting too far away from your original suggestion; I'm just thinking that if I am implementing multiple emojis like you want, these extensions might be just around the corner.

@earnestt1234 Good Idea! I also think it would be better to let the user pass in a function to determine the char/symbol for depth/extension.

Do you have an agenda or bullet point list for it ?

@timweissenfels Here's my proposal:

  • create a new parameter for seedir.folderstructure._folderstructure(), i.e. the general function for diagram function which works for real directories as well as seedir fake ones. The parameter could be formatter (?).
  • For each item in a folder (that is being added to the diagram), this function will be passed an object with attributes for the current depth (e.g. depth), the number of items in the folder (e.g. total), its index in the folder (e.g. index), its file/folder name (e.g. name), and its full path from the root (e.g. path).
  • This object will be an instance of a subclass of str (e.g. SeedirString), in order to attach the attributes described above. The string itself passed to SeedirString will be the full path to the object. This way, someone could work with the object assuming it is just a string system path, but could also use the additional attributes if desired (such as to implement different emojis for depths).
  • The function should return a dictionary, containing any of ['extend', 'split', 'space', 'final', 'folderstart', 'filestart'] as keys, with values being the strings to use for the respective tokens.
  • The dictionary returned by formatter will be used to update a dictionary (e.g. tokens) containing the other tokens determined by the style used during the current call of seedir(). A try-except might be needed here, in case the function does not return something that is dict-like.
  • When writing the line to the diagram, the tokens used will be retrieved from tokens.
  • seedir.realdir.seedir() and seedir.fakedir.FakeDir.seedir() will be updated to handle this new parameter.

So an example would be like:

import seedir as sd

def foo(item):
	
    import os

    folders={0:'πŸ“˜ ', 1: 'πŸ“• ', 2: 'πŸ“• '}
    files={1:'πŸ“ ', 2:'πŸ“„ '}
	
    # the `item` will be a path, so you can treat it like one
    isfolder =  os.path.isdir(item)
	
    d = folders if isfolder else files
    key = 'folderstart' if isfolder else 'filestart'
	
    # but it will also have some additional attributes that can be used
    if d.get(item.depth):
    	return {key: d[item.depth]} # the dict returned is only one element; it doesn't have to cover all tokens
    else
	return {} # if the user doesn't do this, don't throw an error (just use the current style)

# the default 'emoji' style will be used when formatter makes no changes
# e.g. if the folder goes beyond a depth of 2
sd.seedir('path', style='emoji', formatter=foo)

This feature has been committed to the dev branch, and will be added in the next version release (0.3.0). Pretty similar to what I described in my last post, although without the attachment of attributes like depth and whatnot (I have retroactively struck through those points).

Instead, formatter is passed either a system path (i.e. a str) or FakeDir, depending on if you are in real directory or fake directory land. In the former case, the path will be either absolute or from the root of the folder, depending on whether the original path the function was called on was absolute or not.

So with the example proposed use case here, the system-path seedir is a little clumsy (you have to define a function to find the depth of a path relative to another folder):

path = 'my/path' # note this is stored outside the function

def get_depth(path, parent):
    relpath = os.path.relpath(path, parent)
    if relpath == '.':
        return 0
    else:
        return relpath.count(os.sep) + 1

def formatter(item):
    folders={0:'πŸ“˜ ', 1: 'πŸ“• ', 2: 'πŸ“• '}
    files={1:'πŸ“ ', 2:'πŸ“„ '}

    isfolder =  os.path.isdir(item)

    d = folders if isfolder else files
    key = 'folderstart' if isfolder else 'filestart'

    # changes here to call get_depth
    depth = get_depth(item, path)
    if d.get(depth):
    	return {key: d[depth]}
    else:
    	return {}

sd.seedir(path, formatter=formatter)

Alternatively, if you first construct a FakeDir from the path of interest, it is a little easier to check inter-item relationships - and you get a depth attribute automatically:

def formatter(item):
    folders={0:'πŸ“˜ ', 1: 'πŸ“• ', 2: 'πŸ“• '}
    files={1:'πŸ“ ', 2:'πŸ“„ '}

    isfolder = item.isdir() # using method of FakeItem

    d = folders if isfolder else files
    key = 'folderstart' if isfolder else 'filestart'

    depth = item.depth # standard attribute for FakeItem
    if d.get(depth):
    	return {key: d[depth]}
    else:
    	return {}

# make a FakeDir, then seedir
sd.fakedir('my/path').seedir(formatter=formatter)

Really nice! Thank you.
But why does the get_depth function has to be relative to another path?
Why cant it use the depth in contrast to either the absolute or the root one of the folder that was already provided?

Writing a lot here, mostly to have a record of my own thought process. I don't feel very strongly about how I have implemented things, so it may certainly change.

The short of it is that the object getting passed to formatter is a string (str) system path, and I didn't like any ways to try and attach the depth (or other properties) to that string. So I ended up taking more of a DIY approach, e.g. if someone wants to use the depth, they can write a little get_depth function and do so, in service of defining their own formatter.

Just to illustrate:

>>> path = '/Users/earne/Desktop/SmallFolder'
# SmallFolder/
#└─singlefile.txt

# show what is being returned
>>> sd.seedir(path, printout=False, formatter=lambda x : print(x, type(x)))
/Users/earne/Desktop/SmallFolder <class 'str'>
/Users/earne/Desktop/SmallFolder\singlefile.txt <class 'str'>

When I am seeing these paths from the "outside" of the function, I cannot inherently determine the root folder (the folder on which seedir was called). With absolute paths, the root always looks like /Users, no matter what. So in order to access the depth, I (as the user) need to derive that, which is totally doable if I use the folder I designated as the root of my tree diagram. But if in my formatter function, I only see a string like "/Users/earne/Desktop/SmallFolder/singlefile.txt" , I have to provide the root folder somehow in order to get the depth. However, I should know this because I know where I am calling sd.seedir().

Now instead of making the user do this, I could augment the object passed to formatter in order to contain the depth (or other properties which might be useful for formatting). That's what I had initially thought about doing, but decided against for the following reasons (more or less):

  • I think the item depth was really the most compelling property to include (like in your example), but it is also pretty easy to calculate externally if the user writes a function like get_depth() . The other things I mentioned (the index of the item within the folder, the total number of items in the folder) seemed a little less straightforward, since they can change based on other seedir arguments which include/exclude items during building of the diagram.
  • I couldn't decide on a satisfying way to return additional properties like the depth. It seemed a little complicated to try and subclass the builtin str (in order to add attributes), and it seemed more intuitive to me to let formatter see the exact things (system path strings) that are being seen by the algorithm.
  • The purpose of having the whole "FakeDir" side of seedir was to have this more object-oriented folder implementation, where additional properties can be computed based on the folders place within the entire diagram. For depth especially, FakeDir/FakeFile objects already have a depth attribute.

Does this make any sense? I am overthinking things I know... I'll be curious to hear if you have suggestions or thoughts. I think even with the way I have implemented it, formatter is pretty expressive. But I'd be interested to see use cases which are particularly or prohibitively hard. I think its decently likely the syntax/structure of using formatter could change in the future if it seems unpleasant to use.

Just wanted to drop a short comment that I successfully used formatter from the dev_formatter branch to produce a readable tree-like list of files with some custom info about each of them (file size, particular properties related to them etc). Very useful, thanks! I missed a formatting key which I could use to place some info after the file name, but may be I was just reading docs too fast. Thanks again!

@Programmierus Thanks for sharing! Glad to hear that it worked for you. I have just pushed the new version to PyPi which has formatter incorporated, so hopefully you can do the same from there now : )

As for the strings after the filename, that is a great suggestion, and it is in fact not implemented. I will plan to add it to a future version, likely as folderend and fileend parameters. I am guessing this will maybe deprecate the slash parameter. TBD!

Note that for version 0.3.1, the formatter parameter was expanded so that other seedir arguments could be edited dynamically. See here for examples.

And @Programmierus folderend and fileend parameters (**kwargs) are now allowed for setting things at each line.

Closing for now, as I don't have more current plans for expanding this feature.

To be honest, I completely forgot about it, sorry! But great job! Looking forward to using it πŸ‘ @earnestt1234