// // Lame Blog 1.0 // // Features: // Per-day entries // HTML and .txt files supported (pulls header from the file). // Include text support // // // Template macros: // // @BLOG_ENTRIES@ // The blob entries rendered // // TODO: // Add images, so I can do: // @image file // @caption Caption // using System; using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Collections; using System.Globalization; using System.Web; using System.Xml; using System.Xml.Serialization; using Rss; class DayEntry : IComparable { public DateTime Date; public string Body; public string Caption; Blog blog; public string blog_base; const string code_style = "class=\"code\" style=\"border-style: solid; background: #ddddff; border-width: 1px; padding: 2pt;\""; const string code_csharp_style = "class=\"code-csharp\" style=\"border-style: solid; background: #ddddff; border-width: 1px; padding: 2pt;\""; const string shell_style = "style=\"border-style: solid; background: #000000; color: #bbbbbb; border-width: 1px; padding: 2pt;\""; public DayEntry (Blog blog, string file) { this.blog = blog; blog_base = blog.config.BlogWebDirectory; try { ParseDate (file); } catch { Console.WriteLine ("Failed to parse date from filename: " + file); } using (FileStream i = File.OpenRead (file)){ using (StreamReader s = new StreamReader (i, Encoding.UTF8)){ if (file.EndsWith (".html")) Load (s, true); else if (file.EndsWith (".txt")) Load (s, false); } } } void ParseDate (string file) { int p = file.LastIndexOf ("/"); int month; Match match = Regex.Match (file, "(200[0-9])/([A-Za-z]+)-0*([0-9]+)"); int year = Int32.Parse (file.Substring (match.Groups [1].Index, match.Groups [1].Length)); int day = Int32.Parse (file.Substring (match.Groups [3].Index, match.Groups [3].Length)); string month_name = file.Substring (match.Groups [2].Index, match.Groups [2].Length); switch (month_name.ToLower()){ case "jan": month = 1; break; case "feb": month = 2; break; case "mar": month = 3; break; case "apr": month = 4; break; case "may": month = 5; break; case "jun": month = 6; break; case "jul": month = 7; break; case "aug": month = 8; break; case "sep": month = 9; break; case "oct": month = 10; break; case "nov": month = 11; break; case "dec": month = 12; break; default: throw new Exception ("Unknown month: " + month_name + " from: " + file); } Date = new DateTime (year, month, day, 13, 55, 0); Caption = String.Format ("{0:dd} {0:MMM} {0:yyyy}", Date); } void Load (StreamReader i, bool is_html) { bool caption_found = false; bool in_pre = false; StringBuilder sb = new StringBuilder (); string s; while ((s = i.ReadLine ()) != null){ if (!caption_found){ if (is_html){ if (s.StartsWith ("

")){ Caption = Caption + ": " + s.Replace ("

", "").Replace ("

", ""); caption_found = true; continue; } else if (s.StartsWith ("#include")){ sb.Append (Include (s.Substring (9), out Caption)); caption_found = true; continue; } } else { if (s.StartsWith ("@") && !caption_found){ Caption = Caption + ": " + s.Substring (1); caption_found = true; continue; } } } if (!is_html){ if (s == "" && !in_pre) sb.Append ("

"); else if (s.StartsWith ("@")) sb.Append (String.Format ("

{0}

", s.Substring (1))); else if (s.StartsWith ("#pre")) in_pre = true; else if (s.StartsWith ("#endpre")) in_pre = false; else sb.Append (s); } else { if (s.StartsWith ("#include")){ string c; sb.Append (Include (s.Substring (9), out c)); continue; } else if (s.StartsWith ("#pic")){ int idx = s.IndexOf (","); if (idx == -1){ Console.WriteLine ("Wrong #pic command"); continue; } string filename = s.Substring (5, idx-5); string caption = s.Substring (idx + 1); sb.Append (String.Format ("

{2}

", blog_base, filename, caption)); continue; } sb.Append (s); } sb.Append ("\n"); } Body = sb.ToString (); } public int CompareTo (object o) { return Date.CompareTo (((DayEntry) o).Date); } string Include (string file, out string caption) { if (file.StartsWith ("~/")){ file = Environment.GetEnvironmentVariable ("HOME") + "/" + file.Substring (2); } string article_file = "./texts/" + Path.GetFileName (file); File.Copy (file, article_file, true); article_file = article_file.Substring (1); // // Remove header stuff, and include inline, stick a copy // StringBuilder r = new StringBuilder (); caption = ""; Console.WriteLine ("Reading: " + file); using (FileStream i = File.OpenRead (file)){ StreamReader s = new StreamReader (i, Encoding.UTF8); string line; bool output = false; while ((line = s.ReadLine ()) != null){ Match m = Regex.Match (line, "(.*)"); if (m.Groups.Count > 1){ caption = line.Substring (m.Groups [1].Index, m.Groups [1].Length); blog.AddArticle (blog_base + article_file, caption); continue; } if (!output){ if (line == ""){ output = true; r.Append (String.Format ("

{0}: (Article Permalink)

", caption, article_file, blog_base)); } continue; } line = Regex.Replace (line, "class=\"code\"", code_style); line = Regex.Replace (line, "class=\"code-csharp\"", code_csharp_style); line = Regex.Replace (line, "class=\"shell\"", shell_style); r.Append (line); r.Append ("\n"); } } return r.ToString (); } public string PermaLink { get { return String.Format ("archive/{0:yyyy}/{0:MMM}-{0:dd}.html", Date); } } } class Blog { public Config config; ArrayList entries = new ArrayList (); public int Entries { get { return entries.Count; } } public Blog (Config config) { this.config = config; string [] years = Directory.GetDirectories (config.BlogDirectory); foreach (string year in years){ if (year.EndsWith (".svn")) continue; string [] days = Directory.GetFiles (year); foreach (string file in days){ if (!(file.EndsWith (".html") || file.EndsWith (".txt"))) continue; entries.Add (new DayEntry (this, file)); } } Console.WriteLine ("Loaded: {0} days", entries.Count); entries.Sort (); } static DateTime LastDate = new DateTime (2004, 5, 19, 0, 0, 0); void Render (StreamWriter o, int idx, string blog_base, bool include_daily_anchor, bool is_archive) { DayEntry d = (DayEntry) entries [idx]; string anchor = HttpUtility.UrlEncode (d.Date.ToString ()).Replace ('%','-').Replace ('+', '-'); if (include_daily_anchor || d.Date < LastDate) o.WriteLine (String.Format ("", anchor)); o.WriteLine ("

{1}

", d.PermaLink, d.Caption, blog_base); o.WriteLine ("
" + d.Body + "
"); // if (!is_archive) // o.WriteLine ("
Comments
", d.PermaLink); } void Render (StreamWriter o, int start, int end, string blog_base, bool include_daily_anchor) { for (int i = start; i < end; i++){ int idx = entries.Count - i - 1; if (idx < 0) return; Render (o, idx, blog_base, include_daily_anchor, end - start == 1); } } void RenderArticleList (StreamWriter o) { foreach (Article a in articles){ o.WriteLine ("{1}
", a.url, a.caption); } } public void RenderHtml (string template, string output, int start, int end, string blog_base) { using (FileStream i = File.OpenRead (template), o = File.Create (output)){ StreamReader s = new StreamReader (i, Encoding.UTF8); StreamWriter w = new StreamWriter (o, new UTF8Encoding (false)); string line; while ((line = s.ReadLine ()) != null){ switch (line){ case "@BLOG_ENTRIES@": Render (w, start, end, blog_base, output == "all.html"); break; case "@BLOG_ARTICLES@": RenderArticleList (w); break; default: line = line.Replace ("@BASEDIR@", blog_base); line = line.Replace ("@TITLE@", config.Title); line = line.Replace ("@DESCRIPTION@", config.Description); line = line.Replace ("@RSSFILENAME@", config.RSSFileName); w.WriteLine (line); break; } } w.Flush (); } } public void RenderArchive (string template) { for (int i = 0; i < Entries; i++){ DayEntry d = (DayEntry) entries [i]; RenderHtml (template, d.PermaLink, Entries - i - 1, Entries - i, "../../"); } } RssChannel MakeChannel () { RssChannel c = new RssChannel (); c.Title = config.Title; c.Link = new Uri (config.BlogWebDirectory + config.BlogFileName); c.Description = config.Description; c.Copyright = config.Copyright; c.Generator = "lb#"; c.ManagingEditor = config.ManagingEditor; c.PubDate = System.DateTime.Now; return c; } public void RenderRSS (RssVersion version, string output, int start, int end) { RssChannel channel = MakeChannel (); for (int i = start; i < end; i++){ int idx = entries.Count - i - 1; if (idx < 0) continue; DayEntry d = (DayEntry) entries [idx]; RssItem item = new RssItem (); item.Author = config.Author; item.Description = d.Body + "\n
Comments
\n"; item.Guid = new RssGuid (); item.Guid.Name = config.BlogWebDirectory + d.PermaLink; //config.BlogWebDirectory + "all.html#" + HttpUtility.UrlEncode (d.Date.ToString ()); item.Link = new Uri (item.Guid.Name); item.Guid.PermaLink = DBBool.True; item.PubDate = d.Date; item.Title = d.Caption; channel.Items.Add (item); } FileStream o = File.Create (output); RssWriter w = new RssWriter (o, new UTF8Encoding (false)); w.Version = version; w.Write (channel); w.Close (); } public class Article { public string url, caption; public Article (string u, string c) { url = u; caption = c; } } ArrayList articles = new ArrayList (); public void AddArticle (string url, string caption) { articles.Add (new Article (url, caption)); } public void RenderRSS (string output, int start, int end) { RenderRSS (RssVersion.RSS20, output + ".rss2", start, end); } } class LB { static void Main () { Config config = (Config) new XmlSerializer (typeof (Config)).Deserialize (new XmlTextReader ("config.xml")); Blog b = new Blog (config); b.RenderHtml ("template", config.BlogFileName, 0, 30, ""); // b.RenderHtml ("template", "all.html", 0, b.Entries, ""); b.RenderArchive ("template"); b.RenderRSS (config.RSSFileName, 0, 30); //File.Copy ("log-style.css", "texts/log-style.css", true); } }