tdwright / contabs

Simple yet flexible tables for console apps.
MIT License
54 stars 20 forks source link

Feature: allow tables to be "refreshed" with a set of data #54

Open tdwright opened 6 years ago

tdwright commented 6 years ago

In some use cases it might be helpful to allow a table to be redrawn with new data. For instance, I may want to grab new data every 5 seconds, clear the console and redraw the table with this new data.

It would be great if a we had a method like table<T>.Refresh(IEnumerable<T> NewData).

For this to work well, we would need to preserve everything except the data. I can think of two areas where this may be problematic:

  1. Column widths - perhaps we'd only preserve these if they'd been explicitly set? Would have to experiment. It might be hard to read a table if the columns keep growing and shrinking.
  2. Calculated columns - we'd need to mark them as computed and store the function, so we could recalculate the values when we get new data.

So it'd be a fair chunk of work, but I think this would be a really interesting addition.

Any thoughts?

pykong commented 6 years ago

A related feature: It would be great if we could just add rows. We work with long running tasks which spill out new data on the fly, thus not all data is available on initial rendering.

Hence it would be great if we could just append new rows to an existing table. Does the API offer any way to suppress the header generation, as this would make it easy to implement.

tdwright commented 6 years ago

@pykong That's an interesting suggestion. Let me better understand your ideal use-case...

  1. You'd write a table, as normal
  2. Receive new data
  3. Create a new table with just the new data
  4. Write this new table without the headers, so it can be appended to the first table

I can see how this would be useful and I think this should be something we support.

In both this new case and the original concept (i.e. table.Refresh()) we'd need some way of carrying some table meta-data (formatting, column widths, etc.) forward. In both cases I think the easiest approach would be to continue working with the same Table object. I think this implies the following:

In your case, would you want .Append() to immediately return the string representation of the new data?

pykong commented 6 years ago

Here is a link to a very hacky implementation of the desired feature based on ConsoleTables https://snippets.cacher.io/snippet/58da2060292f7cdb44b9

tdwright commented 6 years ago

Thanks @pykong. I hope we can make it less hacky by offering a method explicitly for this purpose.

Do you fancy having a stab at this? If not, I'll have a go when I next get a chance.

pykong commented 6 years ago

@tdwright I am short on time.

However If ... you refer explicitly to a method for append rows in the Console itself (not to a refresh of the whole table object) ... you implemented a way to suppress the generation of the table header.

Then I could then use that as a starting point.

The functionality that I seek need to give only the table body. Hence both header and the delimiter between header and table body need to be omitted.

tdwright commented 6 years ago

@pykong No worries. I'll have a look at this at some point soon. (I'm away next week, so it won't be immediate.)

maxwell9999 commented 5 years ago

Hi @tdwright, I am rather new to .NET (C#) and open source but saw you list this on up for grabs and think it's something I could tackle. Would love your direction on getting started. I could take a stab at this sometime this weekend.

tdwright commented 5 years ago

Hi @maxwell9999,

I like your enthusiasm!

If I were to tackle this, I'd break it into the following discrete tasks:

Might be quite a big job, so feel free to push your branch as you go along so that others (e.g. me) can play along.

Good luck! Tom

maxwell9999 commented 5 years ago

Ok, I've taken my first look at the code and run through the demos.

My approach will be to start with the Refresh and Append methods first as those are the new functionality, with the other two steps being supporting pieces that can come later.

For Refresh: The idea here is save some of the effort done in initial table creation, right? So I will be adding a non-static method in the Table class which takes in an IEnumerable<T> of data and when called by a table object will modify the data in the table to reflect the new data and throw away the old. I am expecting the user to give me data that matches the old data in terms of type T in regards to columns and things like that, right? So the method will be similar to table creation except that I can skip the setup steps and instead replace them with an input-sanitization-like check to ensure the new data will fit in the table.

For Append: Follow pykong's suggestions above, I will be implementing a method that is very similar to Refresh but instead of replacing any data, it will (after a check to confirm valid data) add to the data and then print the new data as normal except omitting the header and the header separator line. That will make it appear as an append to the already printed table.

Does that make sense to you, @tdwright?

Obviously, this will be a bit easier to discuss once there's some code to look at, so I will try to have some kind of initial solution/prototype push to my fork as soon as possible (hopefully by tomorrow, if not, then Tuesday).

Best, Maxwell

tdwright commented 5 years ago

Hi @maxwell9999,

I think that's the bones of it, yes.

The reason I suggested them in that order is that you'd need to do them first in order to reliably refresh/append. Well, need might be a bit strong...

With regards to validating that the shape of the data is the same, we should get this for free via the magic of generics. When we create a Table<T>, that instance retains it's knowledge of what T is. This means we can write our new methods to accept an IEnumerable<T> and be confident that everything will "fit".

Finally, with regards to the Append() method, I think we're both on the same page when it comes to what the output should be. I think we can state that de-duping the data is out of scope of a table rendering utility, so we can assume that whatever we've been passed is new and therefore needs to be written. (This fits with what I understand of @pykong's use case, but perhaps they disagree?)

Anyway, it sounds like you're off to a great start. Have a go at throwing some code together and I'd be very happy to review it.

Tom

pykong commented 5 years ago

@tdwright I agree that deduping data is not in the scope of this project. If this was your question after all.

@maxwell9999 Kudos for stepping forward on this. I am usually short on time but feel free to summon me on any PR so I can have a look into it.

maxwell9999 commented 5 years ago

I definitely agree that locking the column widths and handling calculated columns is important (as you stated in the original post in this issue) and didn't mean to ignore your suggested ordering. I guess in my mind it just seems easier to implement the new features first and then go back and fix up all the parts of the code that don't support them. It seems like both the width locking and calculation persistence aren't currently necessary; they are additions related to the new methods. And, perhaps we'll discover other parts that need to be fixed up along the way. Unless, you'd like to integrate the new code iteratively, in which case, yes, it does make sense to do the support work first so that when the new feature is introduced it works fully. I was planning to see this through, committing each step along the way and then fixing the issue as a whole with one PR. Would you rather have me PR each incremental change?

And, d'oh, of course, once the table is created the T is no longer generic. Pass it a list of objects type not-T and it will not accept it. Thanks for that reminder.

Appreciate all the other clarifications and encouragement. Hope to have something shortly.

tdwright commented 5 years ago

Sorry @maxwell9999, I didn't mean to suggest that you had to do things in the order I suggested. Just wanted to ensure you'd fully grokked why I'd suggested they get done. You clearly do, so the order isn't really important. You've obviously thought it through and I'm happy to proceed in whatever manner you'd prefer. 💯

And I'm more than OK with us working on this on one branch and via one PR. We can keep pushing new changes to the branch and the PR will update to reflect this. In fact, this would be preferable to a whole load of smaller PRs.

You may have noticed that I've included this feature in the v2 milestone. That's because I feel that this (along with #60 in particular) represent a significant chunk of new functionality in the project. I mention this so that you know that I appreciate the scale of this issue, and I'm very willing to be hands-on.

Speaking of being hands-on, I wonder whether we could persuade @Chandler-Davidson to come help us with re-working the calculated columns functionality? Whaddya say Chandler? For old-times sake? 😉

tdwright commented 5 years ago

Hi @maxwell9999! Hope you're well?

Just wondering how things are going on this issue? If you wanted to get some early feedback, that offer still stands. Or, if you're happy plugging away for the time being, feel free to tell me to bugger off! 😁

Tom