Nov 16
prelude.go
Hey there! Are you a programmer who is interested in becoming more productive? Welcome!
welcome.png
This post is just the "prelude", where I create the blogging engine to publish my blogs. So this post won't interest you (unless you want to create a similar blog) Instead, you should head out and check my later posts.
bye.png
Still here? Ok then, let's dive in.
A fair question to ask is just why (as a so-called "productive programmer"), would I write a blogging engine rather than just use the dozens of options available - from the amazingly powerful WordPress to the light and speedy Jekyll.
It's just silly...isn't it?
Well I agree with you - it is pretty dumb. Except for two things: 1. I want each blog post to not just be me rambling about theoretical ideas, but to produce something of actual value that works. 2. If I am a productive programmer then - heck - it shouldn't take me more than a few hours to create a simple blogging engine. Right?
And because it shouldn't take more than a few fun hours to create and it lets me create a blog which delivers actual working project with every post (this first blog will be a downloadable and usable engine by itself), I've decided to just go ahead and build it.
done.png
[...] I'm done with the engine! It's now usable and (as you can see) is generating the blog you are reading. It took a bit longer than expected but not too much and I'm quite happy with the way it turned out. What follows is the code that eats itself (yum!) to create this lovely page and can be found in this file.
[ ] So what do we have to do? We'll take a config file with paths to the blog posts and use it to load the post information and generate the blog. Simple enough? Let's begin...
package main import ( "os" "log" "io/ioutil" "strings" "regexp" "time" "errors" "os/exec" "path/filepath" "text/template" "html" "strconv" "fmt" ) func main() { postinfo,err := load_config_info() if err != nil { log.Fatal(err); } posts,err := set_post_info(postinfo) if err != nil { log.Fatal(err); } generate_blog(posts); }
[=] Generate the blog [ ] The blog is generated simply by: (a) Generating an "about" page (a) Generating a first/index page with links to (c) Generated blog posts
func generate_blog(posts []PostInfo) { generate_about_page(posts) generate_blog_index(posts) generate_blog_posts(posts) } var LINE_MARKER string = "[\n\r]+" var WHITESPACE string = "[ \t]" /* [:typ:] */ type post_comment_marker struct { start string decorate rune end string } type postcontent_type int const ( EMPTY postcontent_type = iota POSTCOMMENT CODE ) type PostContent struct { Typ postcontent_type HTMLVal string } type PostInfo struct { InPath string On time.Time Content []PostContent AssetDir string AddlCss []string AddlJs []string pcm post_comment_marker OutPath string Tags string HTMLTitle string }
[...] The config file contains 1. The list of paths to each blog post 2. Optional path for the blog's assets (images & css). 3. Optional tags posts/timemgmt/timemgmt.c, tm-assets posts/learn-angular/angularstart.htm posts/some/val.go, val-assets, (golang) ...
func load_config_info() ([]PostInfo,error) { cfg, err := get_config_file() if err != nil { return nil,err } var data []byte data, err = ioutil.ReadFile(cfg) lines := regexp.MustCompile(LINE_MARKER).Split(string(data), -1) var r []PostInfo for _,line := range lines { line = strings.TrimSpace(line) if len(line) > 0 { r = append(r, cfg_post_paths(line)) } } return r,nil }
[=] Load post paths from the given configuration line. This has one of three formats: (a) just/a/blog/post.c (b) a/blog/post.c, with-assets/ (c) a/blog/post.c, with-assets/, (and tags) If assets are provided, we look for additional files and add them as well. [ ] Try and split the line on a comma. [ ] If we can't just set the InPath and we're done [ ] If we can, make sure we only have a max of three splits - the blog path, the asset dir, and tags. [ ] Walk the asset directory and look for additional files (CSS and JS)
func cfg_post_paths(cfg_line string) PostInfo { r := PostInfo{} s := strings.Split(cfg_line, ",") if len(s) == 1 { return PostInfo{ InPath: filepath.Clean(strings.TrimSpace(cfg_line)) } } if len(s) > 3 { s = []string{ strings.Join(s[:len(s)-1], ","), s[len(s)-1] } } if len(s) > 1 { r.InPath = filepath.Clean(strings.TrimSpace(s[0])) r.AssetDir = filepath.Clean(strings.TrimSpace(s[1])) } if len(s) > 2 { r.Tags = strings.TrimSpace(s[2]) } paths,err := filepath.Glob(filepath.Join(r.AssetDir, "*.css")) if err == nil { r.AddlCss = paths } paths,err = filepath.Glob(filepath.Join(r.AssetDir, "*.js")) if err == nil { r.AddlJs = paths } return r } /*[=] Get the config file from * the user */ func get_config_file() (string, error) { if len(os.Args) == 1 { return "", errors.New("No config file provided!") } return os.Args[1], nil }
[=] Set the information we need for our posts from the source file. This means: [ ] Set the post date [ ] Set the post content [ ] Set the post out path [ ] Set the post out title
func set_post_info(pi []PostInfo) ([]PostInfo,error) { var err error for ndx := range pi { pi[ndx].On,err = get_post_date(pi[ndx]) if err != nil { return nil,err } pi[ndx].Content,err = get_post_content(pi[ndx]) if err != nil { return nil,err } pi[ndx].OutPath,err = get_outpath(pi[ndx]) if err != nil { return nil,err } pi[ndx].HTMLTitle,pi[ndx].Content,err = get_post_title(pi[ndx]) if err != nil { return nil,err } } return pi,nil } /* [=] Return the output path for the post. [ ] The is the filename + ".php" */ func get_outpath(postinfo PostInfo) (string,error) { return filepath.Base(postinfo.InPath) + ".php" ,nil } /* [:cond:] */ func cond_is_title(c PostContent) bool { v := strings.TrimSpace(c.HTMLVal) return c.Typ == POSTCOMMENT && len(v) > 0 && !strings.Contains(v,"\n") } /* [=] Return the title of the post [ ] If the first content is a POSTCOMMENT with only one line we use that as the title. [ ] Otherwise we use the file name as the title (replacing underscores with spaces) */ func get_post_title(postinfo PostInfo) (string,[]PostContent,error) { if len(postinfo.Content) > 0 && cond_is_title(postinfo.Content[0]) { t := strings.TrimSpace(postinfo.Content[0].HTMLVal) return t,postinfo.Content[1:],nil } return fname_to_title(filepath.Base(postinfo.InPath)),postinfo.Content,nil } /* [=] Convert file name to a title-like string */ func fname_to_title(fname string) string { return template.HTMLEscapeString(strings.Replace(fname, "_", " ", -1)) }
[!] The post date is not explicitly set. And because the post repositories are replicated across dev and production, they do not share a date. Therefore setting a post date can be a little tricky. [+] We first try to get a date from git. This is not perfect as git doesn't track file date so we use the latest commit information as a proxy. [+ -] When starting a new post, the file is not in git and does not contain commit information. So we default to file modification time as a fallback.
func get_post_date(postinfo PostInfo) (time.Time,error) { var t time.Time filedir := filepath.Dir(postinfo.InPath) currdir,err := os.Getwd() if err == nil { err := os.Chdir(filedir) if err == nil { fname := filepath.Base(postinfo.InPath) out,err := exec.Command("git", "log", "--reverse", "--format=%ad", "--date=short", "--", fname).Output() os.Chdir(currdir) dates := strings.Split(string(out), "\n") date := strings.TrimSpace(dates[0]) if err == nil && len(date) > 0 { t,err = time.Parse("2006-01-02", date) if err != nil { return t, errors.New("Failed to parse git date: " + date) } return t,nil } } } var fi os.FileInfo fi,err = os.Stat(postinfo.InPath) if err != nil { return t,err } return fi.ModTime(),nil } /* [=] Use the file data to create post content of different types. The steps we follow are: [ ] Find "post block comment marker" for this type of file. - For example: .js files : /** * / .htm files : <!---- --> .nim files : ## ## ... [ ] Read the file data and convert into post blocks and code blocks. [ ] The blocks are processed and returned. */ func get_post_content(postinfo PostInfo) ([]PostContent,error) { var err error postinfo.pcm, err = get_comment_marker(postinfo) if err != nil { return nil, err } var data []byte data, err = ioutil.ReadFile(postinfo.InPath) if err != nil { return nil, err } return process_post_content(split_post_content(data, postinfo), postinfo), nil }
[=] Split the file data into post content [=] The kind of splitting we have to do differs if we have a line type commment: ## This starting marker ## matches the ## ending marker so the ## block ends when the ## marker is missing Or a block type comment: /** This starting marker does not match the ending marker so the block ends when the ending marker is found * / [ ] Check what type of block we have and split appropriatly.
func split_post_content(data []byte, postinfo PostInfo) []PostContent { cond_is_line_type_comment := func(postinfo PostInfo) bool { return postinfo.pcm.start == postinfo.pcm.end } if cond_is_line_type_comment(postinfo) { return split_post_content_linecomments(data, postinfo) } else { return split_post_content_blockcomments(data, postinfo) } }
[=] Split file based on block-type post comments [ ] Convert the data to a string and add guards on both ends so that we can match regular expressions that start with newline without worrying about edge cases. [ ] Loop finding post block comment marker start [ ] All content till the start marker is put into a CODE block [ ] Close the block by finding a line that matches the ending marker. [ ] The content of this block is put as a POSTCOMMENT block and the loop is continued.
func split_post_content_blockcomments(data []byte, postinfo PostInfo) []PostContent { var r []PostContent rx_start := regexp.MustCompile(LINE_MARKER + regexp.QuoteMeta(postinfo.pcm.start)) rx_end := regexp.MustCompile(regexp.QuoteMeta(postinfo.pcm.end)) content := "\n" + string(data) for { m_start := rx_start.FindStringIndex(content) if m_start == nil { r = append(r, PostContent{ Typ: CODE, HTMLVal: content }) return r; } if m_start[0] > 0 { r = append(r, PostContent{ Typ: CODE, HTMLVal: content[:m_start[0]] }) } content = content[m_start[1]:] m_end := rx_end.FindStringIndex(content) if m_end == nil { r = append(r, PostContent{ Typ: POSTCOMMENT, HTMLVal: content }) return r; } r = append(r, PostContent{ Typ: POSTCOMMENT, HTMLVal: content[:m_end[0]] }) content = content[m_end[1]:] } }
[=] Split file based on line-type post comments [ ] Split the content into lines [ ] Start with an accumulator of "empty line" type [ ] While the current line is of the same type, continue to accumulate it. [ ] If the current line is of a different type, add a new record of the existing accumulator and start a new accumulator of the new type [ ] When all lines are over, create a record of the remaining accumulator
func split_post_content_linecomments(data []byte, postinfo PostInfo) []PostContent { var r []PostContent rx := regexp.MustCompile(regexp.QuoteMeta(postinfo.pcm.start)) rx_line_ending := regexp.MustCompile("\n|\r|\n\r|\r\n") lines := rx_line_ending.Split(string(data), -1) content_type := func(line string) postcontent_type { line = strings.TrimSpace(line) if len(line) == 0 { return EMPTY } if rx.FindStringIndex(line) != nil { return POSTCOMMENT } return CODE } type accum_ struct { typ postcontent_type cnt []string } accum := accum_{} accum_lines := func(line string) { accum.cnt = append(accum.cnt, line) } empty_accum := func(typ postcontent_type) { if accum.typ != EMPTY { r = append(r, PostContent{ Typ: accum.typ, HTMLVal: "\n" + strings.Join(accum.cnt,"\n") }) } accum.typ = typ accum.cnt = []string{} } for _,line := range lines { typ := content_type(line) if typ != accum.typ { empty_accum(typ) } accum_lines(line) } empty_accum(EMPTY) return r }
[!] The post content contains markup-like text I would like to use: link text https://www. youtube.com/watch?v=XXXXXX some_pic .png some_pic .png [href=.] bold* italic bold-italic class1* __class2__* ___class3___* ... [!] The content also contains text that needs to be escaped in order to form valid HTML (like <, >, &, etc...) [+] Escape the content of all text, look for remaining patterns and replace with the appropriate HTML. ie: [ ] First we clean the post content of any decorators. [ ] Escape HTML for all blocks [ ] If the block is not POSTCOMMENT type, we're done [ ] Otherwise, find the relevant markup and replace it.
func process_post_content(pcs []PostContent, postinfo PostInfo) []PostContent { var r []PostContent for _,pc := range pcs { pc.HTMLVal = clean_post_content(pc, postinfo.pcm.decorate) pc.HTMLVal = template.HTMLEscapeString(pc.HTMLVal) if pc.Typ == POSTCOMMENT { pc.HTMLVal = replace_markup(pc.HTMLVal, postinfo) } r = append(r, pc) } return r }
[=] Post content sometimes contain decorators: /** Some Text with Deocorators * / which we need to clean up
func clean_post_content(pc PostContent, decorater rune) string { rx := regexp.MustCompile(LINE_MARKER + WHITESPACE + "*" + regexp.QuoteMeta(string(decorater)) + "+" + WHITESPACE + "?") if pc.Typ == POSTCOMMENT { return rx.ReplaceAllString(pc.HTMLVal, "\n") } else { return pc.HTMLVal } }
[=] Replace all markup within the content. link text https://www. youtube.com/watch?v=XXXXXX some_pic .png some_pic .png [href=.] bold* italic bold-italic class1* __class2__* ___class3___* ... [+] Find the appropriate regular expressions, and replace them. [+ -] The tricky bit is to not replace expressions that contain URL's. For example: href=/the/_best_/part should NOT become href=/the/<i>best</i>/part [+] So what we'll do is save the url's in an array and temporarily index them by using $$$$<num>$$$$, which (hopefully) should never be found in our text. [ ] Find all matches starting with URL matches (so we can safetly save them away). [ ] Replace each match with the appropriate text (and escaped URL markers) [ ] When all matches are done, find and replace all URL markers.
func replace_markup(s string, postinfo PostInfo) string { type from_to struct { from string to func(s string, m []int) string } type save_urls struct { top int urls []string } saved_urls := save_urls{} /* [=] Save a URL and return a temporary $$$$<num>$$$$ url to be used until it is replaced */ save_url := func(save *save_urls, url string) string { url = html.UnescapeString(url) save.top += 1 save.urls = append(save.urls, url) return `$$$$` + strconv.Itoa(save.top-1) + `$$$$` } link_replacer := func(s string, m []int) string { tmp_url := save_url(&saved_urls, s[m[2]:m[3]]) path := s[m[4]:m[5]] return `<a href="` + tmp_url + `">` + path + `</a>` } youtube_replacer := func(s string, m []int) string { tmp_url := save_url(&saved_urls, s[m[2]:m[3]]) return `<iframe class=vid src="https://www.youtube.com/embed/` + tmp_url + `" frameborder="0" allowfullscreen></iframe>` } /*[!] We need to copy the images in each repository to the current directory. [+] Show a copy message so this can be done manually TODO: automate this */ pic_replacer := func(s string, m []int) string { url := html.UnescapeString(s[m[2]:m[3]]) imgsrc := filepath.Join(filepath.Dir(postinfo.InPath), url) imgdst := filepath.Join(postinfo.AssetDir, url) fmt.Println("cp '" + imgsrc + "' '" + imgdst + "'") alt := fname_to_title(url) tmp_url := save_url(&saved_urls, imgdst) return `<img class=pic src="` + tmp_url + `" alt="` + template.HTMLEscapeString(alt) + `"></img>` } pic_link_replacer := func(s string, m[]int) string { tmp_url := save_url(&saved_urls, s[m[4]:m[5]]) img := pic_replacer(s, m) return `<a href="` + tmp_url + `">` + img + `</a>` } bold_italic_replacer := func(s string, m []int) string { return s[m[2]:m[3]] + `<i><b>` + s[m[4]:m[5]] + `</b></i>` } bold_replacer := func(s string, m []int) string { return s[m[2]:m[3]] + `<b>` + s[m[4]:m[5]] + `</b>` } italic_replacer := func(s string, m []int) string { return s[m[2]:m[3]] + `<i>` + s[m[4]:m[5]] + `</i>` } class_replacer := func(s string, m []int) string { n := m[5] - m[4] classname := "class" + strconv.Itoa(n) return s[m[2]:m[3]] + `<span class=` + classname + `>` + s[m[6]:m[7]] + `</span>` } ft_maps := []from_to { {from: `\[href=([^]]+)\]\(([^)]+)\)`, to: link_replacer }, {from: LINE_MARKER + WHITESPACE + `*([^ ]*\.jpg)` + WHITESPACE + `*\[href=([^]]+)\]`, to: pic_link_replacer }, {from: LINE_MARKER + WHITESPACE + `*([^ ]*\.png)` + WHITESPACE + `*\[href=([^]]+)\]`, to: pic_link_replacer }, {from: LINE_MARKER + WHITESPACE + `*([^ ]*\.gif)` + WHITESPACE + `*\[href=([^]]+)\]`, to: pic_link_replacer }, {from: LINE_MARKER + WHITESPACE + `*https://www.youtube.com/watch\?v=([^ \t\n\r]*)` + WHITESPACE + `*`, to: youtube_replacer }, {from: LINE_MARKER + WHITESPACE + `*https://youtu.be/([^ \t\n\r]*)` + WHITESPACE + `*`, to: youtube_replacer }, {from: LINE_MARKER + WHITESPACE + `*([^ ]*\.jpg)` + WHITESPACE + `*`, to: pic_replacer }, {from: LINE_MARKER + WHITESPACE + `*([^ ]*\.png)` + WHITESPACE + `*`, to: pic_replacer }, {from: LINE_MARKER + WHITESPACE + `*([^ ]*\.gif)` + WHITESPACE + `*`, to: pic_replacer }, {from: `([ \t\n\r(;.])_\*([A-Za-z0-9](.|\n|\r)*?)\*_`, to: bold_italic_replacer }, {from: `([ \t\n\r(;.])\*([A-Za-z0-9](.|\n|\r)*?)\*`, to: bold_replacer }, {from: `([ \t\n\r(;.])_([A-Za-z0-9](.|\n|\r)*?)_`, to: italic_replacer }, {from: `([ \t\n\r(;.])\*([_]+)([(.A-Za-z0-9](.|\n|\r)*?)[_]+\*`, to: class_replacer }, } for _,ft_map := range ft_maps { rx := regexp.MustCompile(ft_map.from) m := rx.FindStringSubmatchIndex(s) r := "" for m != nil { r += s[:m[0]] + ft_map.to(s, m) s = s[m[1]:] m = rx.FindStringSubmatchIndex(s) } s = r + s } replace_tmp_urls := func(s string, save save_urls) string { rx := regexp.MustCompile(`\$\$\$\$([0-9]+)\$\$\$\$`) m := rx.FindStringSubmatchIndex(s) r := "" for m != nil { ndx,err := strconv.Atoi(s[m[2]:m[3]]) if err != nil || ndx >= len(save.urls) { r += s[:m[1]] s = s[m[1]:] } else { r += s[:m[0]] + save.urls[ndx] s = s[m[1]:] } m = rx.FindStringSubmatchIndex(s) } s = r + s return s } return replace_tmp_urls(s, saved_urls) } /* [=] Return the comment markers for the type of file passed in. TODO: Take inputs from external configuration file. */ func get_comment_marker(postinfo PostInfo) (post_comment_marker,error) { m := map[string]post_comment_marker { ".go": { start: "/**", decorate: '*', end: "*/" }, ".swift": { start: "/**", decorate: '*', end: "*/" }, ".js": { start: "/**", decorate: '*', end: "*/" }, ".nim": { start: "##", decorate: '#', end: "##" }, ".el": { start: ";;", decorate: ';', end: ";;" }, ".java": { start: "/**", decorate: '*', end: "*/" }, ".c": { start: "/**", decorate: '*', end: "*/" }, ".cpp": { start: "/**", decorate: '*', end: "*/" }, } ext := filepath.Ext(postinfo.InPath) markers,ok := m[ext] if !ok { return markers, errors.New("Did not find post comment marker for filetype: " + ext) } return markers,nil }
[=] Generate the main page - a list of blogs in a new index.html file. [ ] We use the small (and quite lovely) go template engine to create this.
const INDEX_TPL=`<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>The Productive Programmer</title> <meta name="description" content="The blog for programmers who are excited about being productive and want to make the best use of their time"> <!-- improve view in mobile --> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> @-ms-viewport{ width: device-width; initial-scale: 1; } </style> <!-- favicons --> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> <link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32"> <link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16"> <link rel="manifest" href="/manifest.json"> <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5"> <meta name="theme-color" content="#ffffff"> <!-- styling reset --> <style> * { margin: 0; padding: 0; font-family: monospace; font-size: 12px; } </style> <!-- style --> <style> .main-content { max-width: 640px; } @media (min-width: 768px) { * { font-size: 14px; } } @media (min-width: 768px) { .main-content { margin-left: 33vw; } } .main-content { margin-top: 0; } .home { margin-bottom: 3em; } .home img { max-width: 64px; text-align: right; } .msg { white-space: pre-wrap; } .date { margin: 0; } .toptitle { margin: 5px 0; } .title { font-weight: bold; margin: 1.67em 0 0.67em 0; } .file { margin: 0.67em 0 3em 0; } .content { white-space: pre-wrap; } .code { white-space: pre; font-size: 75%; color: #999; } .sep { white-space: pre; } .mycomment input { font: serif; font-size:95%; display: block; } .mycomment div { margin: 5px 0; } .comment { max-width: 240px; } .comment * { font-family: serif; max-width: 240px; } .comment div { margin: 5px 0; } .comment .author { font-weight: bold; white-space: pre-wrap; } .post { display: block; margin: 0.5em 0; } @media (max-width: 767px) { .date,.toptitle,.title,.post,.home,.file,.content,.code,.mycomment,.comments { margin-left: 8px; margin-right: 8px; } } </style> <script src='https://www.google.com/recaptcha/api.js'></script> </head> <body> <div class=main-content> <div class=toptitle>The Productive Programmer's Blog</div> <div class=home> <a href=/><img src=prodprog-bw.png alt='logo'></img></a> </div> <div class=msg> In this blog, I want to help programmers like myself be productive, get wonderful things done, and make the best use of our time and effort. <a href=/about.php>more...</a> </div> <div class=title>Posts</div> {{range .}} <span class=post>+ <a href={{urlquery .OutPath}}>{{.HTMLTitle}}</a> {{.Tags}} </span> {{end}} <div class=sep> . . . . . . . . . . </div> </div> <script> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-87972254-1', 'auto'); ga('send', 'pageview'); </script> </body> </html>` func generate_blog_index(pi []PostInfo) error { t,err := template.New("index.html").Parse(INDEX_TPL) if err != nil { return err } i,err := os.Create("index.html") if err != nil { return err } defer i.Close() return t.Execute(i, pi) }
[=] Generate an "about me" page
const ABOUT_TPL=`<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>About Me: The Productive Programmer</title> <meta name="description" content="Learn a bit about me"> <!-- improve view in mobile --> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> @-ms-viewport{ width: device-width; initial-scale: 1; } </style> <!-- favicons --> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> <link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32"> <link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16"> <link rel="manifest" href="/manifest.json"> <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5"> <meta name="theme-color" content="#ffffff"> <!-- styling reset --> <style> * { margin: 0; padding: 0; font-family: monospace; font-size: 12px; } </style> <!-- style --> <style> .main-content { max-width: 640px; } @media (min-width: 768px) { * { font-size: 14px; } } @media (min-width: 768px) { .main-content { margin-left: 33vw; } } div { margin: 3em 0; } .main-content { margin-top: 0; } .home { margin-bottom: 3em; } .logo { max-width: 64px; text-align: right; } .date { margin: 0; } .date a { text-decoration: none; color: black; } .back, .back a { text-decoration: none; color: black; } .title { font-weight: bold; margin: 0.67em 0; } .file { margin: 0.67em 0 3em 0; } .content { white-space: pre-wrap; } .code { white-space: pre; font-size: 75%; color: #999; } .sep { white-space: pre; } .footer,.notify_me { font: serif; font-size: 95%; font-style: italic; } .mycomment input { margin: 5px 0; font: serif; font-size:95%; display: block; } .mycomment input[type=checkbox] { display: inline; } .mycomment div { margin: 5px 0; } .comment { max-width: 240px; } .comment * { font-family: serif; max-width: 240px; } .comment div { margin: 5px 0; } .comment .author { font-weight: bold; white-space: pre-wrap; } @media (max-width: 767px) { .date,.title,.file,.content,.code,.mycomment,.comments,.footer { margin-left: 8px; margin-right: 8px; } } #submit_comment { font-size: 1.2em; } .back,.copyright,.srcfile { margin: 0; } </style> <script src='https://www.google.com/recaptcha/api.js'></script> </head> <body> <div class=main-content> <div class=title>About Me</div> <div class=home> <a href=/><img src=about-me.jpg alt='about me'></img></a><br/> </div> <div class=content> I am a programmer who has always been fascinated by systems and efficiency. I've always believed that the most precious thing we have is time and I truly want to make sure we get the most out of the time we have. As programmers, we spend most of our time - programming! So, naturally, I wanted to find the best and most productive way to handle programming work. I used a LOT of systems trying to figure this out. I've tried <a href=http://gettingthingsdone.com/>Getting Things Done</a>, <a href=https://www.franklincovey.com/execution/>Franklin-Covey's 4DX</a>, <a href=https://www.amazon.com/Eat-That-Frog-Great-Procrastinating/dp/1576754227>Eat that Frog</a>, <a href=http://cirillocompany.de/pages/pomodoro-technique>The Pomodoro System</a>, <a href=https://zenhabits.net/zen-to-done-ztd-the-ultimate-simple-productivity-system/>Zen to Done</a>, <a href=http://dontbreakthechain.com/>Don't break the chain</a>, <a href=https://www.tonyrobbins.com/products/productivity-performance/time-of-your-life/>Tony Robbins Time of Your Life</a>, <a href=https://en.todoist.com/>Todist</a>, <a href=https://www.rememberthemilk.com/>Remember the milk</a>... <img src=too-many.png></img> I'm going to be blogging about my experiences with all these as well. However I'll let you in on the big idea I found - most of these are general systems that apply a lot more to managers than developers! There are a LOT of really good ideas there, but most systems themselves are geared slighly more towards managers than producers. In this blog, I will focus on what I've found works for programmers and programming so if that is something you are also interested in - welcome! </div> <div class=sep> . . . . . . . . . . </div> <script> function enable_submit() { document.getElementById('submit_comment').disabled = false; } </script> <form class=mycomment method=POST> <input type=hidden name=comment_on value=/about.php> <input type=checkbox name=notify_me value=notify> <span class=notify_me>Keep me updated!</span> <input type=text placeholder="Email(never shared)" name=email id=email> <div class="g-recaptcha" data-callback="enable_submit" data-sitekey="6LcCqQwUAAAAAJK_PChDBP28CGsOPlCZ1xkR44hB"></div> <input id=submit_comment disabled=disabled type=submit value="Submit"> </form> <div class=sep> . . . . . . . . . . </div> <div class=footer> <a href=/><img src=prodprog.png class=logo alt='logo'></img></a> <div class=back><a href=/>../</a></div> <div class=copyright>Copyright &copy; <?php echo date("Y"); ?> @productiveprogrammer</div> </div> </div> <script> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-87972254-1', 'auto'); ga('send', 'pageview'); </script> </body> </html>` func generate_about_page(pi []PostInfo) error { t,err := template.New("about.html").Parse(ABOUT_TPL) if err != nil { return err } i,err := os.Create("about.php") if err != nil { return err } defer i.Close() return t.Execute(i, pi) }
[=] Generate all blog posts [ ] We use go templates [ ] We generate php files so that we can dynamically render comments.
func generate_blog_posts(pi []PostInfo) { for _,postinfo := range pi { generate_blog_post(postinfo) } } /* [=] Generate a blog post */ const POST_TPL=`<?php $root = $_SERVER['DOCUMENT_ROOT']; $config = parse_ini_file($root . '/../php-mysql-config.ini'); $conn = mysqli_connect('localhost', $config['username'], $config['password'], $config['dbname']); $username=""; if(! $conn ) { die('Could not connect: ' . mysqli_connect_error()); } if (isset($_COOKIE['username'])) { $username = $_COOKIE['username']; } if ((isset($_POST['comment']) && !empty($_POST['comment'])) || ((isset($_POST['email']) && !empty($_POST['email'])))) { if(isset($_POST['g-recaptcha-response']) && !empty($_POST['g-recaptcha-response'])) { $secret = "6LcCqQwUAAAAAG_Cdcmk_BeSCCttVIpelXCzN6QJ"; $recaptcha = $_POST['g-recaptcha-response']; $url = 'https://www.google.com/recaptcha/api/siteverify'; $data = 'secret=' . $secret . '&response=' . $recaptcha; $ch = curl_init( $url ); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt( $ch, CURLOPT_POST, 1); curl_setopt( $ch, CURLOPT_POSTFIELDS, $data); curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt( $ch, CURLOPT_HEADER, 0); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1); $verifyResponse = curl_exec( $ch ); $responseData = json_decode($verifyResponse); if ($responseData->success) { $notify_me = mysqli_real_escape_string($conn, $_POST['notify_me']); if ($notify_me == "notify") { $inlist = 1; } else { $inlist = 0; } $comment_on = mysqli_real_escape_string($conn, $_POST['comment_on']); $comment = mysqli_real_escape_string($conn, $_POST['comment']); $username = $_POST['email']; $email = mysqli_real_escape_string($conn, $_POST['email']); $addr = mysqli_real_escape_string($conn, $_SERVER['REMOTE_ADDR']); $port = mysqli_real_escape_string($conn, $_SERVER['REMOTE_PORT']); $method = mysqli_real_escape_string($conn, $_SERVER['REQUEST_METHOD']); $url = mysqli_real_escape_string($conn, $_SERVER['REQUEST_URI']); $client_ip = isset($_SERVER['HTTP_CLIENT_IP']) ? mysqli_real_escape_string($conn, $_SERVER['HTTP_CLIENT_IP']) : ''; $x_forwarded_for = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? mysqli_real_escape_string($conn, $_SERVER['HTTP_X_FORWARDED_FOR']) : ''; $ua = isset($_SERVER['HTTP_USER_AGENT']) ? mysqli_real_escape_string($conn, $_SERVER['HTTP_USER_AGENT']) : ''; $referer = isset($_SERVER['HTTP_REFERER']) ? mysqli_real_escape_string($conn, $_SERVER['HTTP_REFERER']) : ''; $sz = isset($_SERVER['CONTENT_LENGTH']) ? mysqli_real_escape_string($conn, $_SERVER['CONTENT_LENGTH']) : ''; if (isset($username)) { $server_name = $_SERVER['SERVER_NAME']; if(0 === strpos($server_name, "www.")) { $server_name = substr($server_name, 3); } setcookie('username', $username, time()+60*60*24*365, '/', $server_name); } $sql = "insert into comments (inlist,live,confirmed,comment_on,comment,email,at,addr,client_ip,x_forwarded_for,port,ua,referer) VALUES('$inlist','1','0','$comment_on','$comment','$email',NOW(),'$addr','$client_ip','$x_forwarded_for','$port','$ua','$referer')"; $retval = mysqli_query($conn, $sql); if (!$retval) { error_log(mysqli_error($conn)); mysqli_close($conn); die("Uh...oh! Something went wrong!"); } } } } ?> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>{{.HTMLTitle}} : The Productive Programmer</title> <meta name="description" content="The blog for programmers who are excited about being productive and want to make the best use of their time"> <!-- improve view in mobile --> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> @-ms-viewport{ width: device-width; initial-scale: 1; } </style> <!-- favicons --> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> <link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32"> <link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16"> <link rel="manifest" href="/manifest.json"> <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5"> <meta name="theme-color" content="#ffffff"> <!-- styling reset --> <style> * { margin: 0; padding: 0; font-family: monospace; font-size: 12px; } </style> <!-- style --> <style> .main-content { max-width: 640px; } @media (min-width: 768px) { * { font-size: 14px; } } @media (min-width: 768px) { .main-content { margin-left: 33vw; } } div { margin: 3em 0; } .main-content { margin-top: 0; } .home { margin: 0 10%; float: right; } .home img { max-width: 64px; } .date { margin: 0; } .date a { text-decoration: none; color: black; } .back, .back a { text-decoration: none; color: black; } .title { font-weight: bold; margin: 0.67em 0; } .file { margin: 0.67em 0 3em 0; } .content { white-space: pre-wrap; } .code { white-space: pre; font-size: 85%; color: #666; } .sep { white-space: pre; } .footer,.notify_me { font: serif; font-size: 95%; font-style: italic; } .mycomment input { margin: 5px 0; font: serif; font-size:95%; display: block; } .mycomment input[type=checkbox] { display: inline; } .mycomment div { margin: 5px 0; } .comment { max-width: 240px; } .comment * { font-family: serif; max-width: 240px; } .comment div { margin: 5px 0; } .comment .author { font-weight: bold; white-space: pre-wrap; } @media (max-width: 767px) { .date,.title,.file,.content,.code,.mycomment,.comments,.footer { margin-left: 8px; margin-right: 8px; } } #submit_comment { font-size: 1.2em; } .back,.copyright,.srcfile { margin: 0; } .txt { white-space: pre-line; } </style> {{range .AddlJs}} <script src="{{.}}"></script> {{end}} {{range .AddlCss}} <link rel=stylesheet href="{{.}}"></link> {{end}} <script src='https://www.google.com/recaptcha/api.js'></script> </head> <body> <div class=home> <a href=/><img src=prodprog-bw.png alt='logo'></img></a> </div> <div class=main-content> <div class=date><a href=/>{{html (post_date .)}}</a></div> <div class=title>{{.HTMLTitle}}</div> <div class=file> <div class=back><a href=/>../</a></div> <div class=srcfile>src/<a href={{github_link .InPath}}>{{html (post_fname .)}}</a></div> </div> {{range .Content}} <div class={{contenttype_class .}}>{{.HTMLVal}}</div> {{end}} <div class=sep> . . . . . . . . . . </div> <script> function enable_submit() { document.getElementById('submit_comment').disabled = false; } </script> <form class=mycomment method=POST> <input type=hidden name=comment_on value="{{urlquery .OutPath}}"> <input type=checkbox name=notify_me value=notify> <span class=notify_me>Notify me on new blog posts</span> <input type=text placeholder="Email(never shared)" name=email id=email value="<?php echo $username?>"> <textarea placeholder="Comment" name=comment cols=24 rows=8></textarea><br/> <div class="g-recaptcha" data-callback="enable_submit" data-sitekey="6LcCqQwUAAAAAJK_PChDBP28CGsOPlCZ1xkR44hB"></div> <input id=submit_comment disabled=disabled type=submit value="Submit"> </form> <div class=sep> . . . . . . . . . . </div> <?php $sql = "select * from comments where TRIM(IFNULL(comment, '')) > '' and comment_on='{{urlquery .OutPath}}' and live=1 order by 'at' desc"; $result = mysqli_query($conn, $sql); if(mysqli_num_rows($result) > 0) { ?> <div class=comments> <?php while($row = mysqli_fetch_assoc($result)) { $email = htmlspecialchars($row['email']); if (!empty($email) && strpos($email, '@')) { $sp = preg_split("/[^A-Za-z]/", $email); $author = $sp[0]; } else { $author = "Someone"; } $comment = htmlspecialchars($row['comment']); echo "<div class=comment>"; echo "<div><span class=author>" . $author . "</span> says:</div>"; echo "<div class=txt>" . $comment . "</div>"; echo "</div>"; } ?> </div> <div class=sep> . . . . . . . . . . </div> <?php } mysqli_close($conn); ?> <div class=footer> <div class=back><a href=/>../</a></div> <div class=copyright>Copyright &copy; <?php echo date("Y"); ?> @productiveprogrammer</div> </div> </div> <script> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-87972254-1', 'auto'); ga('send', 'pageview'); </script> </body> </html>` func generate_blog_post(postinfo PostInfo) error { var fm = template.FuncMap { "post_date" : post_date, "post_fname" : post_fname, "contenttype_class" : contenttype_class, "gitlab_link" : gitlab_link, "github_link" : github_link, } t,err := template.New("post.html").Funcs(fm).Parse(POST_TPL) if err != nil { return err } post,err := os.Create(postinfo.OutPath) if err != nil { return err } defer post.Close() return t.Execute(post, postinfo) } func post_date(postinfo PostInfo) string { return postinfo.On.Format("Jan 02") } func post_fname(postinfo PostInfo) string { return filepath.Base(postinfo.InPath) } func contenttype_class(pc PostContent) string { if pc.Typ == POSTCOMMENT { return "content" } else if pc.Typ == CODE { return "code" } else { return "empty" } } /* [=] Return the link to gitlab. [+] We're going for a quick-and-dirty solution. [+] Rather than trying to find the git path we assume that it is in our post/<repo> path. [ ] Split the path [ ] The second component is the repository. [ ] The remaining components is the path needed. [ ] Create the gitlab link */ var GITLAB_PFX = "https://gitlab.com/productiveprogrammer/" func gitlab_link(path string) string { paths := strings.Split(path, string(filepath.Separator)) repo := paths[1] path = strings.Join(paths[2:], string(filepath.Separator)) return GITLAB_PFX + repo + "/blob/master/" + path } /* [=] Return the link to github. [+] We're going for a quick-and-dirty solution. [+] Rather than trying to find the git path we assume that it is in our post/<repo> path. [ ] Split the path [ ] The second component is the repository. [ ] The remaining components is the path needed. [ ] Create the github link */ var GITHUB_PFX = "https://github.com/theproductiveprogrammer/" func github_link(path string) string { paths := strings.Split(path, string(filepath.Separator)) repo := paths[1] path = strings.Join(paths[2:], string(filepath.Separator)) return GITHUB_PFX + repo + "/blob/master/" + path }
. . . . . . . . . .
Notify me on new blog posts
. . . . . . . . . .
rayala says:
Hi sir, nice to see you here! Interesting blog.. Btw, this is BalaKrishna. :)
theproductiveprogrammer says:
Hi Bala! Great to have you on board! :-)
sInhelullyGype says:
Hello. I'm Jeff
ezpaeBoafefonecy says:
free vegas casino games <a href="https://casinorealmoney.us/">online casino games</a> | <a href=" https://casinorealmoney.us/ ">pch slots</a> | <a href=https://casinorealmoney.us/>slotomania free slots</a> <a href=https://casinorealmoney.us/>casino games online</a>
mvxjnBoafefonecy says:
casino real money <a href="https://onlinecasino.us.org/">firekeepers casino</a> | <a href=" https://onlinecasino.us.org/ ">slots games</a> | <a href=https://onlinecasino.us.org/>virgin online casino</a> <a href=https://onlinecasino.us.org/>casino bonus</a>
xbfjbBoafefonecy says:
high 5 casino <a href="https://onlinecasinodd.com/">slots for real money</a> | <a href=" https://onlinecasinodd.com/ ">online casino games</a> | <a href=https://onlinecasinodd.com/>gold fish casino slots</a> <a href=https://onlinecasinodd.com/>lady luck</a>
fqcbxBoafefonecy says:
virgin online casino <a href="https://onlinecasinodd.com/">online casinos</a> | <a href=" https://onlinecasinodd.com/ ">doubledown casino</a> | <a href=https://onlinecasinodd.com/>slots for real money</a> <a href=https://onlinecasinodd.com/>mgm online casino</a>
igormartyniuk says:
<a href=http://sprintmoney.pl/>Szybka pozyczka</a>
darthvader says:
Showbox is a famous app for Android. It also works for iOS. <a href=http://stay-fresh-kid.tumblr.com/>http://stay-fresh-kid.tumblr.com/</a>
kardio says:
Sir Elton John is my favourite jazz-singer of the world. http://323milesdistance.tumblr.com/
leviev says:
В общем-то, при этом в разе не предполагаемого пропадания связи с Интернетом, не требуется огорчаться, так как вполне реалистично докачивать востребованные файлы, и поэтому без сложностей запустить игру на ноутбуке или PC. Найти https://gamingbooster.ru/ - скачать
tempmailbox says:
<a href=>temp mail send</a> As a Hawaii Marriage ceremony Photographer and getting a Groom myself I have been via the rigors of being in get in touch with with distributors and obtaining promotions from likely marriage ceremony firms whilst getting ready for a marriage ceremony. While I had to discover this lesson the challenging way, I'm providing you this tip so you don't have to. One of the most helpful pieces of guidance I can give to any shopper is to established up a individual short term email account for your marriage organizing and use it to register for staying in speak to with sellers, contests at bridal expos and whenever you are requesting information from a possible seller online.
sheilamaximovna says:
Revolutional update of captcha regignizing package "XRumer 16.0 + XEvil": captcha recognition of Google (ReCaptcha-2 and ReCaptcha-3), Facebook, BitFinex, Bing, Hotmail, SolveMedia, Yandex, and more than 8400 another size-types of captchas, with highest precision (80..100%) and highest speed (100 img per second). You can use XEvil 4.0 with any most popular SEO/SMM programms: iMacros, XRumer, GSA SER, ZennoPoster, Srapebox, Senuke, and more than 100 of other software. Interested? There are a lot of impessive videos about XEvil in YouTube. FREE DEMO AVAILABLE! Good luck!
cathyren says:
internet dating for college students chinese american dating culture <a href=http://www.last.dating-solution.icu/3/151.php>alt scene dating</a> my ex started dating courting dating stages similar dating site like zoosk
ekujwal says:
http://doxycycline-cheapbuy.site/ - doxycycline-cheapbuy.site.ankor <a href="http://onlinebuycytotec.site/">onlinebuycytotec.site.ankor</a>
zibaltuz says:
http://doxycycline-cheapbuy.site/ - doxycycline-cheapbuy.site.ankor <a href="http://onlinebuycytotec.site/">onlinebuycytotec.site.ankor</a>
ubeyozusa says:
http://doxycycline-cheapbuy.site/ - doxycycline-cheapbuy.site.ankor <a href="http://onlinebuycytotec.site/">onlinebuycytotec.site.ankor</a>
ubolaluk says:
http://doxycycline-cheapbuy.site/ - doxycycline-cheapbuy.site.ankor <a href="http://onlinebuycytotec.site/">onlinebuycytotec.site.ankor</a>
great says:
Incredible update of captchas breaking package "XEvil 4.0": captchas solution of Google (ReCaptcha-2 and ReCaptcha-3), Facebook, BitFinex, Bing, Hotmail, SolveMedia, Yandex, and more than 8400 another types of captchas, with highest precision (80..100%) and highest speed (100 img per second). You can use XEvil 4.0 with any most popular SEO/SMM software: iMacros, XRumer, GSA SER, ZennoPoster, Srapebox, Senuke, and more than 100 of other programms. Interested? There are a lot of demo videos about XEvil in YouTube. Free XEvil Demo available. Good luck ;)
remi says:
Ekstrakcja informacji Warszawa świadczy usługi przywracania dostępu do danych po wszystkich typach uszkodzeń nośników, jak: uszkodzenia logiczne typu formatowanie dysku. Wejdź tutaj aby zapoznać się z informacjami dotyczącymi <a href=https://odzyskiwanie-danych-z-dysku.warszawa.pl/>odzyskiwania danych</a>
arkadiybabkin says:
ваш телефон был заблокирован просим пополнить карточку: https://texnoera.com/vash-telefon-byl-zablokirovan-mvd
rebvots says:
Cialis 5 Mg Precio Espana Online Pharmacy Www <a href=http://exdrugs.com>viagra online</a> Propecia Pc Generic Levaquin Canadian Cialis 5 Mg
innamarvina says:
<a href=http://instanu.ru/golaya-asti-1407.html>анна дзюба фото голая</a>
vectraworld says:
Hacienda de San Antonio , un rifugio di lusso situato ai piedi del maestoso Volcan de Fuego in Messico, è lieta di annunciare la nomina di Gonzalo Mendoza come Executive Chef. La stimata carriera di Mendoza abbraccia più di due decenni con un'eccellente esperienza culinaria maturata in alcune delle migliori località del mondo.
matperi says:
Authentic Cialis Diltiazem <a href=http://bmamasstransit.com>cialis 20mg price at walmart</a> Cialis Potenzmittel Rezeptfrei How To Get A Bottle Of Viagra
kelitence says:
Can You Buy Real Viagra Online Kamagra Review <a href=http://sildenafdosage.com>buy viagra</a> Order My Childs Add Medicine Online Baclofene Fr
nox says:
Odzyskiwanie informacji Warszawa oferuje usługi przywracania dostępu do sformatowanych danych po wszelakich rodzajach uszkodzeń nośników, jak: uszkodzenia logiczne typu chkdsk), zniszczenia elektroniczne takie jak: zwarcie układów na płytce elektroniki zewnętrznej HDD oraz uszkodzeń mechanicznych jak np. uszkodzenie komutatora. Wejdź tutaj aby zapoznać się z informacjami dotyczącymi <a href=https://tiny.pl/t9h64>odzyskiwania danych</a>
laas says:
Odzyskiwanie danych Warszawa oferuje usługi przywracania dostępu do danych po jakichkolwiek rodzajach uszkodzeń nośników, jak: zniszczenia logiczne typu przypadkowe usunięcie informacji. Wejdź tutaj aby zapoznać się z informacjami dotyczącymi <a href=https://odzyskiwanie-danych-z-macierzy.warszawa.pl>odzyskiwania danych z macierzy raid</a>
mygoinsbays says:
Hi! We are friends, looking for interesting and fun guys to meet, enjoy your time and have fun sex. We can be found on the website by clicking on our photo Or click the link <a href=http://teachbeta.com/dating.php>We are here</a>. <a href=http://teachbeta.com/dating.php>Best sex dating</a>!!!
alina says:
купить тачскрин leagoo m8: https://smartera.in.ua/touch-screen-leagoo-m8
cs says:
I landed here in search of the-brown-dragon :-)
cs says:
BTW I am Amit.
hdsexfilme says:
bfxxx http://www.bfxxx.mobi http://www.bfxxx.mobi - bfxxx
u says:
Hi, a have one question. What all people doing here? Why we dont living with real life?
ellLuff says:
Cialis Wikipedia <a href=http://viaacost.com>viagra online</a> Clomid Besoin D Aide Grossesse
va says:
Hi My name is Jenny. I trying to find job in model or cinema business. If you have any offers for me, please <a href=https://bit.ly/2y4jPOg>contact me</a> <img src="http://i65.tinypic.com/fm7sxe.jpg">
ellLuff says:
Baclofen Achat 10mg Amoxicillin Uses Cialis Efecto En Mujeres <a href=http://getpharmacyonline.com>cialis online</a> Adderall Ce Que Priligy Generique Buy Diflucan Cheap No Prescription
plaskysara says:
Hello my good. My nickname Lin. Looking for a guy to meet. I will come to your area or meet me. I live close. <a href=http://quespecdico.tk/766b>Click here</a>
jehanberndsen says:
<a href=https://dredsolution.com/buy-viagra-online>buy viagra online</a>
volesandrej says:
Привет всем участникам! прикольный у вас сайт! Нашел прикольную базу кино: <a href=http://kinobunker.net/>Лучшие сериалы список 2018</a> Тут: <a href=http://kinobunker.net/priklyucheniya/>приключения хорошего качества hd</a> лучшие фильмы приключения смотреть онлайн список 2018 Тут: <a href=http://kinobunker.net/anime/>аниме бесплатно лучшее</a> аниме лучшее смотреть рейтинг 2018 Тут: http://kinobunker.net/14569-v-proizvodstve-ekranizaciya-komiksa-o-cheloveke-pugovice.html В производстве экранизация комикса о Человеке-пуговице Тут: http://kinobunker.net/5627-spasibo-papa-thank-you-dad-2014.html
hethertan says:
По совету Обратились сюда <a href=https://india-express.net/shop/83/desc/pemetrexed>пеметрексед +с +к инструкция казахстан</a>
kostenlosepornos says:
bfxxx http://www.bfxxx.mobi
g says:
Katrina-9v@5w.moneyrobotdiagrams.club
robertlek says:
<a href=http://onlinepharmacycanadaus.com/>canadapharmacyonline com</a> prescriptions from canada without <a href=http://onlinepharmacycanadaus.com/>canadian pharmacy express</a>
alfred says:
KISS Blowout Lash, Bouffant <a href=http://pegasbaby.com/com/shop/view/118761/>2 Pack - Always Xtra Protection Daily Liners, Clean Scent, Extra Long 30 ea</a> http://pegasbaby.com/com/shop/view/210549/
robertlek says:
<a href=http://finasteride-propecia.com/>finasteride 5mg</a> propecia results <a href=http://finasteride-propecia.com/>hair growth treatments/#buy propecia pills</a>
robertlek says:
<a href=http://rxviagralo.com/>buy viagra no prescription</a> buy viagra pills online <a href=http://rxviagralo.com/>buy viagra no rx</a>
robertlek says:
<a href=http://locialispl.com/>buy cialis usa</a> where buy cialis <a href=http://locialispl.com/>buy cialis medication/#buy cialis medication</a>
robertlek says:
<a href=http://locialispl.com/>buy tadalafil pills</a> buy cialis usa <a href=http://locialispl.com/>buy cialis usa/#buy cialis medication</a>
robertlek says:
<a href=http://canadianonlinepharmacychs.info/>shopko online pharmacy</a> online pharmacy in canada <a href=http://canadianonlinepharmacychs.info/#>pharmacy online</a>
robertlek says:
<a href=http://onlinepharmacychs.info/>best online pharmacy that does not require a prescription</a> canadian online pharmacy no prescription <a href=http://onlinepharmacychs.info/#>canada drugs review</a>
robertlek says:
<a href=http://onlinepharmacychs.info/>percocet online pharmacy</a> watch tour de pharmacy online <a href=http://onlinepharmacychs.info/#>xanax online pharmacy</a>
robertlek says:
<a href=http://canadianonlinepharmacychs.info/>cvs pharmacy online application</a> pharmacy technician online program <a href=http://canadianonlinepharmacychs.info/#>perscription drugs from canada</a>
robertlek says:
<a href=http://loprozac.info/>purchase fluoxetine</a> order prozac usa <a href=http://loprozac.info/#>order generic prozac</a>
robertlek says:
<a href=http://lolasix.info/>buy furosemide pills</a> lasix <a href=http://lolasix.info/#>lasix generic</a>
robertlek says:
<a href=http://loprozac.info/>buy prozac pills</a> prozac cheap <a href=http://loprozac.info/#>prozac cheap</a>
robertlek says:
<a href=http://lolasix.info/>buy lasix cheap</a> buy lasix <a href=http://lolasix.info/#>buy furosemide no prescription</a>
robertlek says:
<a href=http://loprozac.info/>purchase prozac no prescription</a> prozac cheap <a href=http://loprozac.info/#>buy prozac pills</a>
robertlek says:
<a href=http://lolasix.info/>buy furosemide</a> buy lasix cheap <a href=http://lolasix.info/#>buy lasix usa</a>
robertlek says:
<a href=http://loprozac.info/>buy prozac online</a> order prozac <a href=http://loprozac.info/#>order prozac online without prescription</a>
robertlek says:
<a href=http://lolasix.info/>purchase lasix</a> buy lasix usa <a href=http://lolasix.info/#>lasix online</a>
robertlek says:
<a href=http://loprozac.info/>order prozac cheap</a> order fluoxetine online <a href=http://loprozac.info/#>buy prozac usa</a>
robertlek says:
<a href=http://lolasix.info/>order furosemide</a> buy furosemide no prescription <a href=http://lolasix.info/#>order lasix</a>
robertlek says:
<a href=http://lolasix.info/>buy generic lasix</a> buy lasix online without prescription <a href=http://lolasix.info/#>generic lasix</a>
robertlek says:
<a href=http://lolasix.info/>buy lasix usa</a> where buy lasix <a href=http://lolasix.info/#>buy furosemide</a>
robertlek says:
<a href=http://lolasix.info/>furosemide</a> order lasix <a href=http://lolasix.info/#>purchase furosemide</a>
gsas says:
Crystle_5c@5v.resort-in-asia.com
robertlek says:
<a href=http://lolasix.info/>purchase lasix</a> order lasix <a href=http://lolasix.info/#>order furosemide</a>
robertlek says:
<a href=http://lolasix.info/>where buy lasix</a> buy lasix no rx <a href=http://lolasix.info/#>generic lasix</a>
robertlek says:
<a href=http://lolasix.info/>purchase lasix online no prescription</a> lasix cheap <a href=http://lolasix.info/#>order lasix</a>
robertlek says:
<a href=http://lolasix.info/>lasix 40 mg</a> buy lasix <a href=http://lolasix.info/#>buy lasix with no prescription</a>
robertlek says:
<a href=http://lolasix.info/>furosemide</a> order lasix <a href=http://lolasix.info/#>purchase lasix online no prescription</a>
robertlek says:
<a href=http://www.trustonlinepharmacies.com/>visit this link</a> see this page <a href=http://www.trustonlinepharmacies.com/#>best online canadian pharmacy</a>
robertlek says:
<a href=http://onlinegenepharmacy.com/>look these up</a> best online pharmacy no perscription <a href=http://onlinegenepharmacy.com/#>no prescription best online pharmacy</a>
robertlek says:
<a href=http://onlinegenepharmacy.com/>remote consultation online pharmacies</a> find <a href=http://onlinegenepharmacy.com/#>next page</a>
robertlek says:
<a href=http://www.trustonlinepharmacies.com/>free best online pharmacy technician practice test</a> canadian pharmacy <a href=http://www.trustonlinepharmacies.com/#>pharmacy</a>
robertlek says:
<a href=http://www.trustonlinepharmacies.com/>home page home page</a> costco best online pharmacy <a href=http://www.trustonlinepharmacies.com/#>visit this page</a>
robertlek says:
<a href=http://onlinegenepharmacy.com/>canadian pharmacy viagra</a> generic viagra best online pharmacy <a href=http://onlinegenepharmacy.com/#>discover more</a>
robertlek says:
<a href=http://canadianonlinepharmacyctbm.com/>pharmacy technician online program</a> walmart pharmacy online <a href=http://canadianonlinepharmacyctbm.com/#>abortion pill online pharmacy</a>
robertlek says:
<a href=http://canadian-drugsale.com/>see this here</a> click here to investigate <a href=http://canadian-drugsale.com/#>levitra</a>
robertlek says:
<a href=http://canadianonlinepharmacyctbm.com/>find more</a> try this website <a href=http://canadianonlinepharmacyctbm.com/#>why not check here</a>
robertlek says:
<a href=http://canadian-drugsale.com/>find out</a> go to this site <a href=http://canadian-drugsale.com/#>read this post here</a>
dsfsdcxfxcbcxvbasdfail says:
https://korn-the-nothing-download-album.hatenablog.com https://korn-the-nothing-download-album.hatenablog.com/entry/2019/09/09/225028
robertlek says:
<a href=http://canadianonlinepharmacyctbm.com/>pharmacy technician classes online free</a> silkroad online pharmacy <a href=http://canadianonlinepharmacyctbm.com/#>cure</a>
robertlek says:
<a href=http://canadian-drugsale.com/>moved here</a> click for more <a href=http://canadian-drugsale.com/#>great site</a>
robertlek says:
<a href=http://canadianonlinepharmacyctbm.com/>nginx</a> online <a href=http://canadianonlinepharmacyctbm.com/#>reference</a>
robertlek says:
<a href=http://canadian-drugsale.com/>view it</a> go now <a href=http://canadian-drugsale.com/#>important source</a>
robertlek says:
<a href=http://canadianonlinepharmacyctbm.com/>shop</a> go <a href=http://canadianonlinepharmacyctbm.com/#>india online pharmacy</a>
robertlek says:
<a href=http://canadian-drugsale.com/>browse around here</a> <home> <a href=http://canadian-drugsale.com/#>help</a>
robertlek says:
<a href=http://onlinepharmacycanadaus.com/>my latest blog post</a> go to the website <a href=http://onlinepharmacycanadaus.com/#>doctor</a>
robertlek says:
<a href=http://onlineuspharmacies.party/>visit your url</a> go to these guys <a href=http://onlineuspharmacies.party/#>great site</a>
robertlek says:
<a href=http://onlinepharmacycanadaus.com/>linked web page</a> learn this here now <a href=http://onlinepharmacycanadaus.com/#>pop over to this site</a>
robertlek says:
<a href=http://onlineuspharmacies.party/>basics</a> going here <a href=http://onlineuspharmacies.party/#>find out this here</a>
robertlek says:
<a href=http://onlinepharmacycanadaus.com/>straight from the source</a> try these out <a href=http://onlinepharmacycanadaus.com/#>go to my blog</a>
robertlek says:
<a href=http://onlineuspharmacylo.info/>india online pharmacy</a> click here <a href=http://onlineuspharmacylo.info/#>online pharmacy adderall</a>
robertlek says:
<a href=http://canadianspharmacy.info/>the original source</a> online compounding pharmacy <a href=http://canadianspharmacy.info/#>see this page</a>
robertlek says:
<a href=http://onlineuspharmacylo.info/>from this source</a> her response <a href=http://onlineuspharmacylo.info/#>image source</a>
robertlek says:
<a href=http://canadianspharmacy.info/>see this</a> zenegra <a href=http://canadianspharmacy.info/#>blog</a>
robertlek says:
<a href=http://onlineuspharmacylo.info/>best online pet pharmacy</a> look at more info <a href=http://onlineuspharmacylo.info/#>costco online pharmacy</a>
robertlek says:
<a href=http://canadianspharmacy.info/>like this</a> visit here <a href=http://canadianspharmacy.info/#>you can look here</a>
robertlek says:
<a href=http://onlineuspharmacylo.info/>this content</a> weblink <a href=http://onlineuspharmacylo.info/#>my response</a>
robertlek says:
<a href=http://canadianspharmacy.info/>rx online pharmacy</a> tramadol online pharmacy <a href=http://canadianspharmacy.info/#>pharmacy no prescription</a>
robertlek says:
<a href=http://onlineuspharmacylo.info/>canadian pharmacy ratings</a> read <a href=http://onlineuspharmacylo.info/#>illness</a>
robertlek says:
<a href=http://canadianspharmacy.info/>weblink</a> online pharmacy vicodin <a href=http://canadianspharmacy.info/#>online pharmacy cialis</a>
robertlek says:
<a href=http://canadianspharmacy.info/>extra resources</a> you could check here <a href=http://canadianspharmacy.info/#>online pharmacy</a>
robertlek says:
<a href=http://onlineuspharmacylo.info/>canada pharmacy</a> view <a href=http://onlineuspharmacylo.info/#>pharmacy technician online courses</a>
robertlek says:
<a href=http://canadianspharmacy.info/>published here</a> best canadian online pharmacy reviews <a href=http://canadianspharmacy.info/#>visit our website</a>
robertlek says:
<a href=http://canadianspharmacy.info/>see this page</a> legal online pharmacy <a href=http://canadianspharmacy.info/#>online pharmacy viagra generic</a>
robertlek says:
<a href=http://canadianspharmacy.info/>caremark online pharmacy</a> costco pharmacy online <a href=http://canadianspharmacy.info/#>this website</a>
robertlek says:
<a href=http://canadianspharmacy.info/>click here for more</a> get more <a href=http://canadianspharmacy.info/#>levitra online pharmacy</a>
robertlek says:
<a href=http://cialishql.com/>click this site</a> continue reading this <a href=http://cialishql.com/#>my response</a>
robertlek says:
<a href=http://finasteride-propecia.com/>go</a> website link <a href=http://finasteride-propecia.com/#>recommended site</a>
robertlek says:
<a href=http://finasteride-propecia.com/>website</a> over at this website <a href=http://finasteride-propecia.com/#>go here</a>
robertlek says:
<a href=http://finasteride-propecia.com/>helpful resources</a> clicking here <a href=http://finasteride-propecia.com/#>navigate to this website</a>
. . . . . . . . . .