June, 2010 Archives
Jun
Using the Toggl API continued…
by Mikael Lundin in Programming
This is a follow up on my previous post where I showed you how to connect to Toggl API with .NET and extract registered tasks.
If we take that data and devide it into weeks, we could plot the amount of work spent each week in a chart with a library like ZedGraph.
As you can see, I started to register my time last week and this week is not finished. By scheduling this chart generation and make sure that the output image is available from an external URL, I can place a chart that displays my activity on the CodePlex dashboard.
I usually look at the commits to see if the project is active, but this would also be a perfect tool. It gives the visitor a hunch how much time was spent during the last five weeks on this project.
Jun
Connect to Toggl API with .NET
by Mikael Lundin in Programming
Here’s the quick and dirty way to extract information out of Toggl API with .NET. You will need NewtonSoft JSON.NET for the JSON parsing, otherwise it’s just cut/paste/run and have fun!
Enjoy!
using System;
using System.IO;
using System.Net;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public class Program
{
private const string TogglTasksUrl = "https://www.toggl.com/api/v1/tasks.json";
private const string TogglAuthUrl = "http://www.toggl.com/api/v1/sessions.json";
private const string AuthenticationType = "Basic";
private const string ApiToken = "ec8soi98983498 your api_token here";
private const string Password = "api_token";
public static void Main(string[] args)
{
CookieContainer container = new CookieContainer();
var authRequest = (HttpWebRequest)HttpWebRequest.Create(TogglAuthUrl);
authRequest.Credentials = CredentialCache.DefaultCredentials;
authRequest.Method = "POST";
authRequest.ContentType = "application/x-www-form-urlencoded";
authRequest.CookieContainer = container;
string value = Password + "=" + ApiToken;
authRequest.ContentLength = value.Length;
using (StreamWriter writer = new StreamWriter(authRequest.GetRequestStream(), Encoding.ASCII))
{
writer.Write(value);
}
var authResponse = (HttpWebResponse)authRequest.GetResponse();
using (var reader = new StreamReader(authResponse.GetResponseStream(), Encoding.UTF8))
{
string content = reader.ReadToEnd();
}
HttpWebRequest tasksRequest = (HttpWebRequest)HttpWebRequest.Create(TogglTasksUrl);
tasksRequest.CookieContainer = container;
var jsonResult = string.Empty;
var tasksResponse = (HttpWebResponse) tasksRequest.GetResponse();
using (var reader = new StreamReader(tasksResponse.GetResponseStream(), Encoding.UTF8))
{
jsonResult = reader.ReadToEnd();
}
var tasks = JsonConvert.DeserializeObject<Task[]>(jsonResult);
foreach (var task in tasks)
{
Console.WriteLine(
"{0} - {1}: {2} starting {3:yyyy-MM-dd HH:mm}",
task.Project.Name,
task.Description,
TimeSpan.FromSeconds(task.Duration),
task.Start);
}
}
public class Task
{
[JsonProperty(PropertyName = "start")]
[JsonConverter(typeof(IsoDateTimeConverter))]
public DateTime Start { get; set; }
[JsonProperty(PropertyName = "stop")]
[JsonConverter(typeof(IsoDateTimeConverter))]
public DateTime Stop { get; set; }
[JsonProperty(PropertyName = "duration")]
public int Duration { get; set; }
[JsonProperty(PropertyName = "description")]
public string Description { get; set; }
[JsonProperty(PropertyName = "project")]
public Project Project { get; set; }
}
public class Project
{
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "client_project_name")]
public string Client { get; set; }
}
}
And the result should look like this.
Jun
Split a string on whole words
by Mikael Lundin in Programming
In case you’ve ever needed an extension method that splits a string on whole words, here are my implementation.
public static class StringExtensions
{
/// <summary>
/// If we only allow complete words the right edge might be a bit ugly.
/// Instead we accept that words are broken if cut has right egde would
/// move more than MaxStringSplitOffset characters for the cut to exist.
/// </summary>
private const int MaxStringSplitOffset = 4;
/// <summary>
/// Splits a string on whole words.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="partitionSize">Size of the partition.</param>
/// <returns>An array of strings not exceeding the partition size</returns>
public static string[] SplitOnWholeWords(this string input, int partitionSize)
{
if (input == null)
{
throw new ArgumentNullException("input");
}
if (partitionSize <= 0)
{
throw new ArgumentOutOfRangeException("partitionSize", "partitionSize must be larger than 0");
}
Contract.EndContractBlock();
var partitioned = new List<string>();
var rest = input;
while (rest.Length > partitionSize)
{
var part = rest.Substring(0, partitionSize);
var cutIndex = part.LastIndexOf(' ');
/* For those cases where next character is ' ' */
if (rest[partitionSize] == ' ')
{
cutIndex = partitionSize;
}
/* No space found */
if (cutIndex == -1)
{
rest = rest.Substring(partitionSize);
}
else if (cutIndex < partitionSize - MaxStringSplitOffset)
{
const int PushCharactersToNextRow = 2;
/* Remove add a dash to the end of the string */
part = part.Substring(0, part.Length - PushCharactersToNextRow) + "-";
/* Remove part from the rest */
rest = rest.Substring(part.Length - 1);
}
else
{
/* Refine cut */
part = part.Substring(0, cutIndex);
/* Remove part from the rest (including the space) */
rest = rest.Substring(cutIndex + 1);
}
partitioned.Add(part);
}
partitioned.Add(rest);
return partitioned.ToArray();
}
}
And some tests to prove that it works.
[TestFixture]
public class SplitOnWholeWords
{
// Split between "fox" and "jumps"
[TestCase("A quick brown fox jumps over the dog", "A quick brown fox", "jumps over the dog")]
// Split will exceed max allowed characters moving to the left
[TestCase("My lover say gregarious, as I stand over her", "My lover say grega-", "rious, as I stand")]
// Split right before the space
[TestCase("All of your base are belong to us", "All of your base are", "belong to us")]
// Split right after the space
[TestCase("##All your base are belong to us", "##All your base are", "belong to us")]
public void ShouldSplitStringInTwo(string original, string expectedLine1, string expectedLine2)
{
/* Test */
var partitions = original.SplitOnWholeWords(20);
/* Assert */
Assert.That(partitions[0], Is.EqualTo(expectedLine1));
Assert.That(partitions[1], Is.EqualTo(expectedLine2));
}
[Test]
public void ShouldHandleWhereInputAndPartitionSizeAreTheSame()
{
/* Setup */
const string Input = "Hello World!";
/* Test */
var partitions = Input.SplitOnWholeWords(Input.Length);
/* Assert */
Assert.That(partitions.Length, Is.EqualTo(1));
}
[Test]
public void ShouldNotTryToSplitWhereThereAreNoSpaces()
{
/* Setup */
const string Input = "TheQuick BrownFoxJumpsOverTheLazyDog";
/* Test */
var partitions = Input.SplitOnWholeWords(10);
/* Assert */
Assert.That(partitions[1], Is.EqualTo("BrownFoxJu"));
Assert.That(partitions[2], Is.EqualTo("mpsOverThe"));
Assert.That(partitions[3], Is.EqualTo("LazyDog"));
}
[Test]
public void CannotSplitNullInputArgument()
{
/* Test */
TestDelegate code = () => ((string) null).SplitOnWholeWords(10);
/* Assert */
Assert.Throws<ArgumentNullException>(code);
}
[Test]
public void CannotSplitWhenPartitionSizeIsNotPositiveNumber()
{
/* Test */
TestDelegate code = () => "Hello World!".SplitOnWholeWords(0);
/* Assert */
Assert.Throws<ArgumentOutOfRangeException>(code);
}
}
Happy coding!


