Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Command line interface: Implement --fields for list command #246

Merged
merged 12 commits into from
Mar 27, 2020

Conversation

earboxer
Copy link
Contributor

@earboxer earboxer commented Mar 22, 2020

#179 (comment)

Things you can do with this

  • python3 -m khard ls -F name,birthday (like khard bdays, but it doesn't sort, doesn't exclude missing birthdays, and outputs in a different format)
  • python3 -m khard ls -pF emails.home.0,name (shows first home email address for each user)
  • python3 -m khard ls --fields phone_numbers.cell.0,phone_numbers (shows first cell number, and all phone numbers in python format)
  • python3 -m khard ls -p -F Name,post_addresses.home.0.region,post_addresses.home.0.code (name, state and zip code)

The list of properties can be seen with

grep '@property' -A1 khard/carddav_object.py

(Additional fields can easily be added by making them attributes in carddav_object.py)

For backwards compatibility with previous usage, the additional fields are defined:

  • index (counts 1,2,3,...)
  • name (formats name as desired)
  • address_book
  • phone
  • email
  • uid (prints minimized when not using --parsable)
  • uid_short

@lucc lucc added enhancement interface changes the user interface in a non trivial way labels Mar 22, 2020
@earboxer
Copy link
Contributor Author

This doesn't refactor any of the existing commands (I'm not sure what our deprecation strategy is for them).

This doesn't break existing functionality of list command.

This doesn't add a way to access first_name. (I'm not sure how we want to design the property for names)

@lucc
Copy link
Owner

lucc commented Mar 22, 2020

Thank you that looks very cool! Can you add a proper doc comment to all your functions and check pep8 formatting, please (I think I saw some function arguments wothout spaces inbetween).

I will do a full review next.

@earboxer
Copy link
Contributor Author

What do you think about the pretty_name function? It exists for the purpose of backwards compatibility (and maybe for the header to look nice?) It also exists so that the fields can be case insensitive... but maybe we would prefer to show the snake_cased ugly name instead?

Copy link
Owner

@lucc lucc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some line comments. For pep8 there are also tools like pylint that you can use.

Nice work so far :)

khard/khard.py Outdated
Comment on lines 367 to 383
attr_name = field.split('.')[0]
val = ''
if hasattr(vcard, attr_name):
val = getattr(vcard, attr_name)
# Nest multiple keys like emails.home.1
for partial in field.split('.')[1:]:
if isinstance(val, dict) and partial in val:
val = val[partial]
elif partial.isdigit() and isinstance(val, list) \
and len(val) > int(partial):
val = val[int(partial)]
# TODO: Completely support case insensitive indexing
elif isinstance(val, dict) and partial.upper() in val:
val = val[partial.upper()]
else:
val = ''
row.append(val)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you could put this in a separate function get_nested_field. That would make it easier to write unit tests for this as it seems like an important piece of new code. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. In what file should the tests be written for this function?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could start a new test class in test/test_command_line_interface.py which is named after the function and has tests for some important cases of its behaviour.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a good idea.

This merge request is getting pretty big though. I don't know when/if I'll get around to writing comprehensive tests for that function.

@lucc
Copy link
Owner

lucc commented Mar 22, 2020

The idea of pretty_name and ugly_name looks good at first but there are some edge cases that do not look nice. Try khard ls -F n-a-----m-e. Maybe it is better to only use alphabetic characters in field names and normalize them with str.lower.

Copy link
Owner

@lucc lucc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please give your type documentation explicitly instead of inline in the text. Many other functions do it that way:

def foo(x, y):
    """This does the foo for bar to baz ...
    
    :param list(str|bool) x: the x that will be fooed
    :param y: that is the bar (with a long type name)
    :type y: some_long_module.and_type_name.Bar
    :returns: 42, always
    :rtype: int
    """
    return 42

@@ -1030,6 +1034,8 @@ def list_subcommand(vcard_list, parsable, fields):
:type vcard_list: list of carddav_object.CarddavObject
:param parsable: machine readable output: columns devided by tabulator (\t)
:type parsable: bool
:param fields: list of strings for field evaluation
:type fields: list
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

list(str)

Copy link
Contributor Author

@earboxer earboxer Mar 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used to generate some documentation somewhere?

Otherwise, this type of repetitious documentation is not helping anyone;
would make the writers of https://www.python.org/dev/peps/pep-0257 sad.

Please update CONTRIBUTING.rst
if this type of documentation is necessary.

(otherwise, feel free to add it yourself).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are used in the sphinx generated documentation. Try make -c doc html && $BROWSER doc/build/html/index.html locally or go to https://khard.readthedocs.io/ . Although the python modules (and these signatures) are not yet visible on readthedocs (only after the next release).

I tend to put a more "human" description in the :param: part and the explicit type signature in the :type: part.

Suggested change
:type fields: list
:param fields: the object path to the field on the vcard that should be printed
:type fields: list(str)

But I can fix these small things when I merge. I know that I am very picky about such things.

About duplication info: I am also thinking about real python type signatures in #204. But that is another big project and seems to bring its own complications.

@earboxer
Copy link
Contributor Author

The idea of pretty_name and ugly_name looks good at first but there are some edge cases that do not look nice. Try khard ls -F n-a-----m-e. Maybe it is better to only use alphabetic characters in field names and normalize them with str.lower.

So then, should the "pretty" formats be TitleCase? (If the header reads "E-mail" as it does, then if someone sees that and wants to use khard -F E-mail, it wouldn't work). This would be a backwards incompatible change (though maybe those only matter for --parsable).

@earboxer
Copy link
Contributor Author

Thank you for all the prompt feedback!

@earboxer
Copy link
Contributor Author

Maybe it is better to only use alphabetic characters in field names and normalize them with str.lower.

Another consideration is that it is pulling attribute names from carddav_object, of which, many have underscores

version
uid
birthday
anniversary
formatted_name
organisations
titles
roles
nicknames
notes
webpages
categories
phone_numbers
emails
post_addresses

@lucc
Copy link
Owner

lucc commented Mar 22, 2020

Is there any special reason you invited my to your khard repository?

I was thinking about normalizing field names and spotting typos:

I suggest that we allow only a very minimal character set in field names for the -F option. Maybe only [a-z] or [a-z_]. But I suggest not to allow -. I see that the header line currently says "E-Mail" but Wikipedia for example spells it Email. And I think that the field names in the header line are only read by humans and not programs. The -p format is modeled after mutt and they don't read the first line at all. They assume it is headers and skip it.

So if a user specifies invalid characters we can give an error message. But what about field names? Try khard ls -F namee. That will result in a bunch of empty lines which is ugly. I have two ideas:

  1. If the fields for a matching contact result in only empty strings (so that the line would be empty) we could skip that contact in the output. So khard ls -F email foo would only output the email address of contacts matching foo and having an email to display. On the other hand khard ls -F name,email foo would output all names and emails of contacts matching foo that have at least a name or an email to show.
  2. We could manage a list of valid fields (derived from the CarddavObject class maybe) and reject any other (top level) fields (nested fields might be harder).

Furthermore I was thinking about private object and other special cases:

I think we can postpone private objects and formatted post addresses and formatted anniversary and birthdays. But I came up with a plan that might be doable after this PR: Add new read only properties to the CarddavObject for these special cases (and other special cases like the different name gettes) so that they can be handled in a uniform way when formating output with fields. Later these can also be used for a custom query syntax like planned in #131.

@lucc
Copy link
Owner

lucc commented Mar 24, 2020

Is there a reason why you have a markdown list token in front of every commit? I would remove them.

@lucc
Copy link
Owner

lucc commented Mar 24, 2020

I like the idea about a fixed list of valid fields more the longer I think about it. We could still ignore case but require one specific spelling (plural or singular, underscores) and given propper error messages.

@earboxer
Copy link
Contributor Author

earboxer commented Mar 25, 2020

Is there a reason why you have a markdown list token in front of every commit? I would remove them.

When I'm committing to a repository that doesn't have a commit message standard, I just go with what I'm used to (what we use at my workplace, which I believe was inspired by Code::Blocks's standard)

-/* System Name: Describe what it does in imperative mood

- means minor commit (documentation, one-line bugfix) * means major commit.

Personally, I don't care much for 'minor commits', and think in general you should make them go away when you git rebase before the merge.

@earboxer
Copy link
Contributor Author

Right now uid appears differently if you use parsable than if you did not.

It is currently the only field that does so (and special logic was written so it does so).

Should we have a specific way of referring to the shortened uid, like uid_short, so that either option can be available in both parsable and non-parsable ways?

@earboxer
Copy link
Contributor Author

It should be noted that 44c090d broke the existing tests...

Copy link
Owner

@lucc lucc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand the reason for a6c2e25 . Why do we need the explicit name?

@lucc
Copy link
Owner

lucc commented Mar 25, 2020

I would ask you to remove the * and - at the beginning of your commit messages. They might make sense if the people reading them know the policy but that is not (yet? :) the policy for this repository so they carry no information for most readers.

The category or section name in the beginning og the commit message is nice and can also be understood by new readers without extra documentation.

@lucc
Copy link
Owner

lucc commented Mar 25, 2020

Currently None is printed for the birthday field if no birthday is set on the contact. This is in contrast to some/all? other fields which print the empty string. I would suggest to make it the empty string for all fields unless there are good arguments for None.

Aditionally we should think about two things related to empty fields (one was already mentioned above):

  1. What happens with a contact that has all requested fields empty? I suggest to drop that contact (it would just produce an empty line).
  2. Should we add some special field modifier to require a value in that field? Something like khard ls --fields 'name,!birthday' 'name,birthday! or any other char we deem acceptable. This would drop any contact from the output that does not have that field set. This would allow us to implement khard bdays as khard ls --fields 'name,birthday!'. An argument against this is that it mixes output logic with search term logic and such a constraint might better be represented in a custom query syntax like proposed in Search within specific fields #131.

@earboxer
Copy link
Contributor Author

Currently None is printed for the birthday field if no birthday is set on the contact. This is in contrast to some/all? other fields which print the empty string. I would suggest to make it the empty string for all fields unless there are good arguments for None.

It's just doing python string conversion: the attribute is returning None. We should probably change the str() method on the value returned so it shows only the day, or the empty string.

What happens with a contact that has all requested fields empty? I suggest to drop that contact (it would just produce an empty line).

khard ls -F phone_numbers.cell | grep '\S'

Show only lines that have non-space-characters. (We would definitely want our None and {} to print as empty string in that case)

khard ls --fields 'name,birthday!'

khard ls --parsable --fields name,birthday | grep -Pv '\tNone'

-P for perl regex (allows the \t to mean tab), -v for inverse.

If we change it to output the empty string instead of 'None', this would be `grep -Pv '\t$' ($ meaning the end of the line).

I believe this project should only be as complicated as necessary...
khard show and khard edit should also reflect the attributes of the vcard object (so the fieldnames are consistent throughout). -- maybe the attributes of the vcard object should change to match khard show... (which would encourage delaying the merge/release of this feature/branch)

@earboxer
Copy link
Contributor Author

I do not understand the reason for a6c2e25 . Why do we need the explicit name?

Was probably a little too overzealous making breaking changes. -- the idea was that the presentation of a field should not change when you change the -p flag (what if you wanted the short uid in a tab-separated output?)

Thinking about it more, it's probably not worth it for having the header change.

reverted in 98b395b

I think the only breaking chance you are authorizing is changing

  • 'E-Mail' to 'Email'
  • 'UID' to 'Uid'

@earboxer
Copy link
Contributor Author

For a web-based project, I have been thinking about Edit-List-View for different fields of a model. In khard, this is pretty similar (except that the command for view is called show, and we have multiple lists for pseudo-models email, postaddress, phone).

Really, khard edit and khard show should be identical, except for the comments provided by edit

Copy link
Owner

@lucc lucc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nearly done :)

The only things still missing are:

After that I think we can merge. 🎉

@earboxer
Copy link
Contributor Author

Okay, I fixed the tests, and rewrote history a little.

@lucc lucc merged commit bf7c195 into lucc:develop Mar 27, 2020
@lucc
Copy link
Owner

lucc commented Mar 27, 2020

Thank you very much @earboxer :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement interface changes the user interface in a non trivial way
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants