Creating a PowerShell Navigation Provider: #3 Nested GetChildItems() Support
In the second post in this series, we covered the basics of acquiring children and outputting them to PowerShell. Next we are going to add some nested navigation and only allow users to navigate into tags that we know exist.
First some administrative tasks. I decided to remove my custom C# Stackoverflow API that was very minimal and use something more robust. I went with StacMan and added it to the project using the nuget package. I then modified the existing code and views to work as before. I also changed the drive name to SO, to make it easier to type and to make it more relevant. With that out of the way, we can now forge ahead.
The final working code for this post can be found the NestedNavigation tag in the github repo.
To start, I modified the tag acquisition to keep the tags around, so I will only load them once and so I can reuse this code where necessary:
private static StacManResponse<Tag> tags;
private void LoadTags(bool forceReload=false)
{
if (tags == null || forceReload)
{
var stackOverflow = new StacManClient();
tags = stackOverflow.Tags.GetAll("stackoverflow.com").Result;
}
}
Next, in my IsItemContainer
, instead of blindly returning true
, I now check to see if the path sent to us is blank (i.e. at the root) or if the path is a tag in our cache. This allows us to navigate into a valid state:
protected override bool IsItemContainer(string path)
{
// The path is empty, so we are at the root. The root is always valid for us.
if (String.IsNullOrEmpty(path))
return true;
LoadTags();
if (tags.Data == null)
return false;
if (tags.Data.Items == null)
return false;
var itemFromTag = from tag in tags.Data.Items
where tag.Name.Equals(path, StringComparison.CurrentCultureIgnoreCase)
select tag;
return itemFromTag.Any();
}
At this point, if you ran the project and started navigating around, you would be able to navigate into first level folders that were valid tags that came back from the API.
Now that I have that working, I am going to modify the GetChildItems
call to return questions for the tag folder you are in. To do this, I am going to simply check the path that is sent to the call and if it is non-empty, then query the questions API:
protected override void GetChildItems(string path, bool recurse)
{
if (string.IsNullOrEmpty(path))
{
// Write the tags
LoadTags();
foreach (var tag in tags.Data.Items)
{
WriteItemObject(tag, tag.Name, false);
}
}
else
{
// Write the questions for this tag
var stackOverflow = new StacManClient();
var questions = stackOverflow.Questions.GetAll("stackoverflow.com", tagged:path);
foreach (var q in questions.Result.Data.Items)
{
WriteItemObject(q, q.Title, false);
}
}
}
At this point questions are returned, but again the output is hideous. Similarly what we did for tags in the last post, we will do for questions. We are going to add a custom view:
<View>
<Name>QuestionView</Name>
<ViewSelectedBy>
<TypeName>StackExchange.StacMan.Question</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Title</Label>
<Width>70</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Creator</Label>
<Width>20</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Reputation</Label>
<Width>10</Width>
<Alignment>Right</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Title</PropertyName>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>$_.Owner.DisplayName</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>$_.Owner.Reputation</ScriptBlock>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
One interesting thing to call out in this view is that we are using the ScriptBlock element to access nested properties (Owner Name and Reputation) on the question object. So now, if you dir
your directory, you will see this:
Title Creator Reputation
----- ------- ----------
C# Clearing a Table user3594691 1
To list and without tolist myfinite 27
EntitySpaces/C#: How to use a subquery in ... brad 11
SQL query search for all the data by month... user3337393 1
Unity control object via socket Udp c# iboy15 6
multicolumn combo box in asp.net nomi ikon 6
C# How can I start PhantomJS + Selenium wi... Tiago Castro 1
Override Json Serialization RavenDB Shea 101
How to get "Windows Phone Pivot Page&... n179911 3017
How to write binary data to serial port in C# Stefan Vogel 1
Can't get the last row of excel when u... Wen Ling Lo 1
Position Window before created using shell... Mark Robbins 1113
Since we are in PowerShell, we get a lot of querying functionality for free, so you can try things like:
#questions from people with rep over 100
dir so:\c# | where {$_.Owner.Reputation -gt 100}
#questions with an answer
dir so:\xml | where {$_.AnswerCount -gt 0}
#questions that have a positive score
dir | where {$_.Score -gt 0}
As you can see combining PowerShell semantics with data from an API can be an interesting way for your users to access your data. In our next post, we will look at tab completion, so we can behave similarly to other drives within a PowerShell environment.