Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial revision

svn path=/trunk/lb/; revision=18986
  • Loading branch information...
commit 59a9375740ddbf1ff1315aa8e1b5f34fbb6085ea 0 parents
Miguel de Icaza migueldeicaza authored
37 README
@@ -0,0 +1,37 @@
+This is Lame Blog.
+
+ A small script that I wrote to maintain my blog on the web. I
+ did not use another blog system, because I had different
+ requirements than other people have.
+
+ I wanted:
+
+ * To support off-line editing. Since for long periods
+ of time I might not have an internet connection.
+
+ * To be able to edit my blog entries with my choice
+ editor, not using some integrated tool.
+
+ * To keep my old .txt file format for my entries.
+
+ * To do some minimal processing on the input.
+
+ The script has plenty of data hardcoded, I should make this
+ configurable some day.
+
+ It looks for blog entries in:
+
+ ~/activity/YYYY/mmm-dd.{html|txt}
+
+ Ie, for example:
+
+ ~/activity/2003/oct-01.html
+
+ For historic reasons, it generates an all.html (compatibility
+ with old permalinks) and also now it generates per-day
+ permalink entries in the archive/ directory.
+
+ The result is then pushed to a server with rsync.
+
+Enjoy,
+Miguel de Icaza (miguel@gnu.org)
379 lb.cs
@@ -0,0 +1,379 @@
+//
+// 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
+//
+//
+
+using System;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Collections;
+using System.Globalization;
+using System.Web;
+using Rss;
+
+class DayEntry : IComparable {
+ public DateTime Date;
+ public string Body;
+ public string Caption;
+ Blog blog;
+
+ public const string blog_base = "http://primates.ximian.com/~miguel/";
+ const string code_style = "style=\"border-style: solid; background: #ddddff; border-width: 1px; padding: 2pt;\"";
+ const string shell_style = "style=\"border-style: solid; background: #000000; color: #777777; border-width: 1px; padding: 2pt;\"";
+
+ public DayEntry (Blog blog, string file)
+ {
+ this.blog = blog;
+ ParseDate (file);
+
+ using (FileStream i = File.OpenRead (file)){
+ using (StreamReader s = new StreamReader (i, Encoding.GetEncoding (28591))){
+ 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-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){
+ 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, 12, 0, 0);
+ Caption = String.Format ("{0:dd} {0:MMM} {0:yyyy}", Date);
+ }
+
+ void Load (StreamReader i, bool is_html)
+ {
+ bool caption_found = false;
+ StringBuilder sb = new StringBuilder ();
+ string s;
+
+ while ((s = i.ReadLine ()) != null){
+ if (!caption_found){
+ if (is_html){
+ if (s.StartsWith ("<h1>")){
+ Caption = Caption + ": " + s.Replace ("<h1>", "").Replace ("</h1>", "");
+ 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 == "")
+ sb.Append ("<p>");
+ else if (s.StartsWith ("@"))
+ sb.Append (String.Format ("<h1>{0}</h1>", s.Substring (1)));
+ else
+ sb.Append (s);
+ } else {
+ if (s.StartsWith ("#include")){
+ string c;
+ sb.Append (Include (s.Substring (9), out c));
+ 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 = "";
+
+ using (FileStream i = File.OpenRead (file)){
+ StreamReader s = new StreamReader (i, Encoding.GetEncoding (28591));
+ string line;
+ bool output = false;
+
+ while ((line = s.ReadLine ()) != null){
+ Match m = Regex.Match (line, "<title>(.*)</title>");
+ 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 == "<!--start--!>"){
+ output = true;
+ r.Append (String.Format ("<h3>{0}: (<a href=\"{2}{1}\">Article Permalink</a>)</h3>", caption, article_file, blog_base));
+ }
+ continue;
+ }
+ line = Regex.Replace (line, "id=\"code\"", code_style);
+ line = Regex.Replace (line, "id=\"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 {
+ ArrayList entries = new ArrayList ();
+
+ public int Entries {
+ get {
+ return entries.Count;
+ }
+ }
+
+ public Blog ()
+ {
+ string [] years = Directory.GetDirectories ("/home/miguel/activity");
+
+ foreach (string year in years){
+ 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 ();
+ }
+
+ void Render (StreamWriter o, int idx, string blog_base)
+ {
+ DayEntry d = (DayEntry) entries [idx];
+
+ string anchor = HttpUtility.UrlEncode (d.Date.ToString ());
+ o.WriteLine (String.Format ("<a name=\"{0}\"></a>", anchor));
+ o.WriteLine ("<h2><a href=\"{2}{0}\" class=\"entryTitle\">{1}</a> <font size=-2>(<a href=\"{2}{0}\">Permalink</a>)</font></h2>",
+ d.PermaLink, d.Caption, blog_base);
+ o.WriteLine (d.Body);
+ }
+
+ void Render (StreamWriter o, int start, int end, string blog_base)
+ {
+ for (int i = start; i < end; i++){
+ int idx = entries.Count - i - 1;
+ if (idx < 0)
+ return;
+
+ Render (o, idx, blog_base);
+ }
+ }
+
+ void RenderArticleList (StreamWriter o)
+ {
+ foreach (Article a in articles){
+ o.WriteLine ("<a href=\"{0}\">{1}</a><p>", 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.GetEncoding (28591));
+ StreamWriter w = new StreamWriter (o, Encoding.GetEncoding (28591));
+ string line;
+
+ while ((line = s.ReadLine ()) != null){
+ switch (line){
+ case "@BLOG_ENTRIES@":
+ Render (w, start, end, blog_base);
+ break;
+ case "@BLOG_ARTICLES@":
+ RenderArticleList (w);
+ break;
+
+ default:
+ line = line.Replace ("@BASEDIR@", blog_base);
+ 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 = "Miguel de Icaza";
+ c.Link = new Uri ("http://primates.ximian.com/~miguel/activity-log.php");
+ c.Description = "Miguel de Icaza's web log";
+ c.Copyright = "Miguel de Icaza";
+ c.Generator = "lb#";
+ c.ManagingEditor = "miguel@ximian.com";
+ 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 = "Miguel de Icaza (miguel@ximian.com)";
+ item.Description = d.Body;
+ item.Guid = new RssGuid ();
+ item.Guid.Name = "http://primates.ximian.com/~miguel/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 ()
+ {
+ Blog b = new Blog ();
+
+ b.RenderHtml ("template", "activity-log.php", 0, 30, "");
+ b.RenderHtml ("template", "all.html", 0, b.Entries, "");
+ b.RenderArchive ("template");
+
+ b.RenderRSS ("miguel", 0, 30);
+
+ File.Copy ("log-style.css", "texts/log-style.css", true);
+ }
+}
39 log-style.css
@@ -0,0 +1,39 @@
+
+ div#entries {
+ font-size: 12pt;
+ margin-left: 2em;
+ margin-right: 35%;
+ float: left;
+ }
+
+ div#entries h2 {
+ background-color: #ddddff;
+ }
+
+ #sidebar {
+ position: absolute;
+ right:10px;
+ left: 70%;
+ top: 6em;
+ }
+
+ #title {
+ background-color: #ccccff;
+ }
+
+ .code { border-style: solid; background: #ddddff;
+ border-width: 1px; padding: 2pt; }
+
+ .shell { border-style: solid; background: #000000; color: #bbbbbb;
+ #777777; border-width:
+ 1px; padding: 2pt; }
+
+A.entryTitle:link {
+text-decoration : none;
+color : 003382;
+}
+
+A.entryTitle:visited {
+text-decoration : none;
+color: black;
+}
10 makefile
@@ -0,0 +1,10 @@
+
+lb.exe: lb.cs
+ mcs -g lb.cs -out:lb.exe -r:RSS.NET -r:System.Web
+
+b: lb.exe
+ mono --debug lb.exe
+
+push: b
+ chmod 644 archive/*/*.html
+ rsync -pr -v --rsh=ssh texts archive log-style.css miguel.rss2 activity-log.php all.html primates.ximian.com:public_html
62 template
@@ -0,0 +1,62 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+<title>Miguel de Icaza's Activity Log</title>
+<link rel="stylesheet" href="@BASEDIR@log-style.css" type="text/css">
+<link rel="alternate" type="application/rss+xml" title="RSS" href="@BASEDIR@miguel.rss2">
+</head>
+<body>
+<div id="title"><h1>Miguel de Icaza's web log</h1></div>
+
+<div id="entries">
+@BLOG_ENTRIES@
+
+
+<p>
+<a href="all.html">All entries</a>
+</div>
+
+<div id="sidebar">
+<img src="http://www.go-mono.com/images/mono-contributor-static.gif">
+<p>
+<a href="miguel.rss2"><img align="center" src="@BASEDIR@xml.gif"></a> RSS feed<br>
+
+<p>Email: <a href="mailto:miguel@ximian.com">miguel@ximian.com</a>
+
+<p><b>Docs:</b></p>
+@BLOG_ARTICLES@
+
+<p><b>Mono Blogs:</b></p>
+
+ <a href="http://codeblogs.ximian.com/blogs/benm/index.rdf"><img align="center" src="@BASEDIR@xml.gif"></a>
+ <a href="http://codeblogs.ximian.com/blogs/benm/">Ben Maurer</a>
+ <br>
+ <a href="http://ada.fciencias.unam.mx/~olopez/jscript/cesar.rss2"><img align="center" src="@BASEDIR@xml.gif"></a>
+ <a href="http://ada.fciencias.unam.mx/~olopez/jscript/">Cesar Nataren</a>
+ <br>
+ <a href="http://primates.ximian.com/~duncan/blog/index.rdf"><img align="center" src="@BASEDIR@xml.gif"></a>
+ <a href="http://primates.ximian.com/~duncan/blog">Duncan Mak</a>
+ <br>
+ <a href="http://www.jacksonh.net/jackson/blog/jackson.rss2"><img align="center" src="@BASEDIR@xml.gif"></a>
+ <a href="http://www.jacksonh.net/jackson/blog/">Jackson Harper</a>
+ <br>
+ <a href="http://primates.ximian.com/~lluis/blog/index.rdf"><img align="center" src="@BASEDIR@xml.gif"></a>
+ <a href="http://primates.ximian.com/~lluis/blog/">Lluis Sanchez</a>
+ <br>
+ <a href="http://primates.ximian.com/~martin/blog/index.rdf"><img align="center" src="@BASEDIR@xml.gif"></a>
+ <a href="http://primates.ximian.com/~martin/blog/">Martin Baulig</a>
+ <br>
+ <a href="http://primates.ximian.com/~mkestner/blog/index.rdf"><img align="center" src="@BASEDIR@xml.gif"></a>
+ <a href="http://primates.ximian.com/~mkestner/blog/">Mike Kestner</a>
+ <br>
+ <a href="http://pages.infinit.net/ctech/poupou.rss"><img align="center" src="@BASEDIR@xml.gif"></a>
+ <a href="http://pages.infinit.net/ctech/poupou.html">Sebastien Pouliot</a>
+
+
+<p>
+<a href="http://msdn.microsoft.com/events/pdc"><img src="http://weblog.ikvm.net/pdc2003.gif"></a>
+</div>
+
+
+</body>
+</html>
BIN  xml.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Please sign in to comment.
Something went wrong with that request. Please try again.