├── .gitignore ├── .travis.yml ├── project.clj ├── LICENSE ├── test └── feedparser_clj │ └── test │ └── core.clj ├── README.md ├── src └── feedparser_clj │ └── core.clj └── resources └── fixtures └── gonzih-blog.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .nrepl-port 3 | .lein-failures 4 | .lein-repl-history 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk7 3 | - oraclejdk7 4 | - oraclejdk8 5 | 6 | language: clojure 7 | script: lein test 8 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject org.clojars.gnzh/feedparser-clj "0.6.0" 2 | :description "Parse RSS/Atom feeds with a simple, clojure-friendly API." 3 | :dependencies [[org.clojure/clojure "1.8.0"] 4 | [org.jdom/jdom2 "2.0.6"] 5 | [net.java.dev.rome/rome "1.0.0"]] 6 | :main feedparser-clj.core) 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Greg Heartsfield 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of contributors may not be used to endorse or promote 18 | products derived from this software without specific prior 19 | written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /test/feedparser_clj/test/core.clj: -------------------------------------------------------------------------------- 1 | (ns feedparser-clj.test.core 2 | (:import [com.sun.syndication.io SyndFeedInput XmlReader] 3 | [java.net URL] 4 | [java.io InputStreamReader] 5 | [com.sun.syndication.feed.synd SyndFeed]) 6 | (:require [feedparser-clj.core :refer :all :reload true] 7 | [clojure.test :refer :all])) 8 | 9 | (defn load-feed-fixture [name] 10 | (str (clojure.java.io/resource (format "fixtures/%s" name)))) 11 | 12 | (deftest parse-test 13 | (let [pf (parse-feed (load-feed-fixture "gonzih-blog.xml"))] 14 | (testing :feed 15 | (is (= (-> pf :author) "gonzih@gmail.com (Max Gonzih)")) 16 | (is (= (-> pf :categories) [])) 17 | (is (= (-> pf :contributors) [])) 18 | (is (= (-> pf :entry-links) [])) 19 | (is (= (-> pf :image) nil)) 20 | (is (= (-> pf :copyright) "This work is licensed under a Creative Commons Attribution 4.0 International License.")) 21 | (is (= (-> pf :description) "Recent content on Max Gonzih")) 22 | (is (= (-> pf :encoding) nil)) 23 | (is (= (-> pf :feed-type) "rss_2.0")) 24 | (is (= (-> pf :language) "en-us")) 25 | (is (= (-> pf :link) "http://blog.gonzih.me/index.xml")) 26 | (is (= (-> pf :published-date) #inst "2015-12-11T00:00:00.000-00:00")) 27 | (is (= (-> pf :title) "Max Gonzih")) 28 | (is (= (-> pf :uri) nil))) 29 | 30 | (testing :entry 31 | (is (= (-> pf :entries count) 15)) 32 | (let [entry (-> pf :entries first)] 33 | (is (= (:authors entry) [])) 34 | (is (= (:categories entry) [])) 35 | (is (= (:contributors entry) [])) 36 | (is (= (:enclosures entry) [])) 37 | (is (= (:contents entry) [])) 38 | (is (= "text/html" (:type (:description entry)))) 39 | (is (re-find #"Collection of tweaks that I gathered after installing Arch.*" (:value (:description entry)))) 40 | (is (= (:author entry) "gonzih@gmail.com (Max Gonzih)")) 41 | (is (= (:link entry) "http://blog.gonzih.me/blog/2015/12/11/arch-linux-on-lenovo-ideapad-y700-15/")) 42 | (is (= (:published-date entry) #inst "2015-12-11T00:00:00.000-00:00")) 43 | (is (= (:title entry) "Arch Linux on Lenovo IdeaPad Y700 15\"")) 44 | (is (= (:updated-date entry) nil)) 45 | (is (= (:uri entry) "http://blog.gonzih.me/blog/2015/12/11/arch-linux-on-lenovo-ideapad-y700-15/")))))) 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | feedparser-clj [![Build Status](https://travis-ci.org/Gonzih/feedparser-clj.svg?branch=master)](https://travis-ci.org/Gonzih/feedparser-clj) 2 | ============== 3 | 4 | Parse RSS/Atom feeds with a simple, clojure-friendly API. 5 | Uses the Java ROME library, wrapped in StructMaps. 6 | 7 | Status 8 | ------ 9 | 10 | Usable for parsing and exploring feeds. No escaping of potentially-malicious content is performed, and we've inherited any quirks that ROME itself has. 11 | 12 | Supports the following syndication formats: 13 | 14 | * RSS 0.90 15 | * RSS 0.91 Netscape 16 | * RSS 0.91 Userland 17 | * RSS 0.92 18 | * RSS 0.93 19 | * RSS 0.94 20 | * RSS 1.0 21 | * RSS 2.0 22 | * Atom 0.3 23 | * Atom 1.0 24 | 25 | Usage 26 | ----- 27 | 28 | For a more detailed understanding about supported feed types and meanings, the ROME javadocs (under [`com.sun.syndication.feed.synd`](https://rome.dev.java.net/apidocs/0_8/com/sun/syndication/feed/synd/package-summary.html)) are a good resource. 29 | 30 | There is only one function, `parse-feed`, which takes a URL and returns a StructMap with all the feed's structure and content. 31 | 32 | The following REPL session should give an idea about the capabilities and usage of `feedparser-clj`. 33 | 34 | Load the package into your namespace: 35 | 36 | user=> (ns user (:require [feedparser-clj.core] [clojure.string :as string])) 37 | 38 | Retrieve and parse a feed: 39 | 40 | user=> (def f (parse-feed "http://gregheartsfield.com/atom.xml")) 41 | 42 | `parse-feed` also accepts a java.io.InputStream for reading from a file or other sources (see [clojure.java.io/input-stream](http://richhickey.github.com/clojure/clojure.java.io-api.html#clojure.java.io/input-stream)): 43 | 44 | ;; Contents of resources/feed.rss 45 | 46 | ... 47 | 48 | 49 | user=> (def f (with-open 50 | [feed-stream (-> "feed.rss" 51 | clojure.java.io/resource 52 | clojure.java.io/input-stream)] 53 | (parse-feed feed-stream))) 54 | 55 | `f` is now a map that can be accessed by key to retrieve feed information: 56 | 57 | user=> (keys f) 58 | (:authors :author :categories :contributors :copyright :description :encoding :entries :feed-type :image :language :link :entry-links :published-date :title :uri) 59 | 60 | A key applied to the feed gives the value, or nil if it was not defined for the feed. 61 | 62 | user=> (:title f) 63 | "Greg Heartsfield" 64 | 65 | Feed/entry ID or GUID can be obtained with the `:uri` key: 66 | 67 | user=> (:uri f) 68 | "http://gregheartsfield.com/" 69 | 70 | Some feed attributes are maps themselves (like `:image`) or lists of structs (like `:entries` and `:authors`): 71 | 72 | user=> (map :email (:authors f)) 73 | ("scsibug@imap.cc") 74 | 75 | Check how many entries are in the feed: 76 | 77 | user=> (count (:entries f)) 78 | 18 79 | 80 | Determine the feed type: 81 | 82 | user=> (:feed-type f) 83 | "atom_1.0" 84 | 85 | Look at the first few entry titles: 86 | 87 | user=> (map :title (take 3 (:entries f))) 88 | ("Version Control Diagrams with TikZ" "Introducing cabal2doap" "hS3, with ByteString") 89 | 90 | Find the most recently updated entry's title: 91 | 92 | user=> (first (map :title (reverse (sort-by :updated-date (:entries f))))) 93 | "Version Control Diagrams with TikZ" 94 | 95 | Compute what percentage of entries have the word "haskell" in the body (uses `clojure.string`): 96 | 97 | user=> (let [es (:entries f)] 98 | (* 100.0 (/ (count (filter #(string/substring? "haskell" 99 | (:value (first (:contents %)))) es)) 100 | (count es)))) 101 | 55.55555555555556 102 | 103 | Installation 104 | ------------ 105 | 106 | This library uses the [Leiningen](http://github.com/technomancy/leiningen#readme) build tool. 107 | 108 | ROME and JDOM are required dependencies, which may have to be manually retrieved and installed with Maven. After that, simply clone this repository, and run: 109 | 110 | lein install 111 | 112 | License 113 | ------- 114 | 115 | Distributed under the BSD-3 License. 116 | 117 | Copyright 118 | --------- 119 | 120 | Copyright (C) 2010 Greg Heartsfield 121 | -------------------------------------------------------------------------------- /src/feedparser_clj/core.clj: -------------------------------------------------------------------------------- 1 | (ns feedparser-clj.core 2 | (:import [com.sun.syndication.io SyndFeedInput XmlReader] 3 | [java.net URL] 4 | [java.io InputStreamReader] 5 | [com.sun.syndication.feed.synd SyndFeed]) 6 | (:gen-class)) 7 | 8 | (defrecord feed [authors author categories contributors copyright description 9 | encoding entries feed-type image language link entry-links 10 | published-date title uri]) 11 | 12 | (defrecord entry [authors author categories contents contributors description 13 | enclosures link published-date title updated-date url]) 14 | 15 | (defrecord enclosure [length type uri]) 16 | (defrecord person [email name uri]) 17 | (defrecord category [name taxonomy-uri]) 18 | (defrecord content [type value]) 19 | (defrecord image [description link title url]) 20 | (defrecord link [href hreflang length rel title type]) 21 | 22 | (defn- obj->enclosure 23 | "Create enclosure struct from SyndEnclosure" 24 | [e] 25 | (map->enclosure {:length (.getLength e) 26 | :type (.getType e) 27 | :url (.getUrl e)})) 28 | 29 | (defn- obj->content 30 | "Create content struct from SyndContent" 31 | [c] 32 | (map->content {:type (.getType c) 33 | :value (.getValue c)})) 34 | 35 | (defn- obj->link 36 | "Create link struct from SyndLink" 37 | [l] 38 | (map->link {:href (.getHref l) 39 | :hreflang (.getHreflang l) 40 | :length (.getLength l) 41 | :rel (.getRel l) 42 | :title (.getTitle l) 43 | :type (.getType l)})) 44 | 45 | (defn- obj->category 46 | "Create category struct from SyndCategory" 47 | [c] 48 | (map->category {:name (.getName c) 49 | :taxonomy-uri (.getTaxonomyUri c)})) 50 | 51 | (defn- obj->person 52 | "Create a person struct from SyndPerson" 53 | [sp] 54 | (map->person {:email (.getEmail sp) 55 | :name (.getName sp) 56 | :uri (.getUri sp)})) 57 | 58 | (defn- obj->image 59 | "Create image struct from SyndImage" 60 | [i] 61 | (map->image {:description (.getDescription i) 62 | :link (.getLink i) 63 | :title (.getTitle i) 64 | :url (.getUrl i)})) 65 | 66 | (defn- obj->entry 67 | "Create feed entry struct from SyndEntry" 68 | [e] 69 | (map->entry {:authors (map obj->person (seq (.getAuthors e))) 70 | :categories (map obj->category (seq (.getCategories e))) 71 | :contents (map obj->content (seq (.getContents e))) 72 | :contributors (map obj->person (seq (.getContributors e))) 73 | :enclosures (map obj->enclosure (seq (.getEnclosures e))) 74 | :description (if-let [d (.getDescription e)] (obj->content d)) 75 | :author (.getAuthor e) 76 | :link (.getLink e) 77 | :published-date (.getPublishedDate e) 78 | :title (.getTitle e) 79 | :updated-date (.getUpdatedDate e) 80 | :uri (.getUri e)})) 81 | 82 | (defn- obj->feed 83 | "Create a feed struct from a SyndFeed" 84 | [f] 85 | (map->feed {:authors (map obj->person (seq (.getAuthors f))) 86 | :categories (map obj->category (seq (.getCategories f))) 87 | :contributors (map obj->person (seq (.getContributors f))) 88 | :entries (map obj->entry (seq (.getEntries f))) 89 | :entry-links (map obj->link (seq (.getLinks f))) 90 | :image (if-let [i (.getImage f)] (obj->image i)) 91 | :author (.getAuthor f) 92 | :copyright (.getCopyright f) 93 | :description (.getDescription f) 94 | :encoding (.getEncoding f) 95 | :feed-type (.getFeedType f) 96 | :language (.getLanguage f) 97 | :link (.getLink f) 98 | :published-date (.getPublishedDate f) 99 | :title (.getTitle f) 100 | :uri (.getUri f)})) 101 | 102 | (defn- parse-internal [xmlreader] 103 | (let [feedinput (new SyndFeedInput) 104 | syndfeed (.build feedinput xmlreader)] 105 | (obj->feed syndfeed))) 106 | 107 | (defn ->url [s] 108 | (if (string? s) (URL. s) s)) 109 | 110 | (defn parse-feed "Get and parse a feed from a URL" 111 | ([feedsource] 112 | (parse-internal (XmlReader. (->url feedsource)))) 113 | ([feedsource content-type] 114 | (parse-internal (XmlReader. (->url feedsource) content-type))) 115 | ([feedsource content-type lenient] 116 | (parse-internal (XmlReader. (->url feedsource) content-type lenient))) 117 | ([feedsource content-type lenient default-encoding] 118 | (parse-internal (XmlReader. (->url feedsource) content-type lenient default-encoding)))) 119 | -------------------------------------------------------------------------------- /resources/fixtures/gonzih-blog.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Max Gonzih 5 | http://blog.gonzih.me/index.xml 6 | Recent content on Max Gonzih 7 | Hugo -- gohugo.io 8 | en-us 9 | gonzih@gmail.com (Max Gonzih) 10 | gonzih@gmail.com (Max Gonzih) 11 | This work is licensed under a Creative Commons Attribution 4.0 International License. 12 | Fri, 11 Dec 2015 00:00:00 +0000 13 | 14 | 15 | 16 | Arch Linux on Lenovo IdeaPad Y700 15" 17 | http://blog.gonzih.me/blog/2015/12/11/arch-linux-on-lenovo-ideapad-y700-15/ 18 | Fri, 11 Dec 2015 00:00:00 +0000 19 | gonzih@gmail.com (Max Gonzih) 20 | http://blog.gonzih.me/blog/2015/12/11/arch-linux-on-lenovo-ideapad-y700-15/ 21 | <p>Collection of tweaks that I gathered after installing Arch Linux on to Lenovo IdeaPAD Y700.</p> 22 | 23 | <p></p> 24 | 25 | <h1 id="what-works">What works</h1> 26 | 27 | <ul> 28 | <li>WIFI</li> 29 | <li>Suspend (look at the bumblebee issue with suspend if nvidia module gets loaded on resume)</li> 30 | <li>Hibernate</li> 31 | <li>Sound without subwoofer</li> 32 | <li>Video (I used bumblebee to switch between intel/nvidia GPUs)</li> 33 | <li>Brightness</li> 34 | <li>Keyboard backlit</li> 35 | <li>Power managment via laptop mode tools and systemd.</li> 36 | <li>Card reader (you might need to run <code>echo 1 | sudo tee /sys/bus/pci/rescan</code> so card reader becomes visible)</li> 37 | <li>HDMI output</li> 38 | </ul> 39 | 40 | <h1 id="what-does-not-work-so-far">What does not work so far</h1> 41 | 42 | <ul> 43 | <li>Subwoofer</li> 44 | </ul> 45 | 46 | <h1 id="installation">Installation</h1> 47 | 48 | <h2 id="boot">Boot</h2> 49 | 50 | <ul> 51 | <li>Add nomodeset to the kernel options on Live USB boot</li> 52 | <li>Make sure secure boot is disabled (to make your life easier)</li> 53 | <li>Follow <a href="https://wiki.archlinux.org/index.php/Installation_guide">arch linux installation instructions</a></li> 54 | </ul> 55 | 56 | <h1 id="extra-tweaking">Extra tweaking</h1> 57 | 58 | <h2 id="video">Video</h2> 59 | 60 | <ul> 61 | <li>Make sure switchable GPUs are enabled in BIOS</li> 62 | <li>Follow <a href="https://wiki.archlinux.org/index.php/Bumblebee#Installing_Bumblebee_with_Intel.2FNVIDIA">insructions on the arch wiki</a></li> 63 | <li>To enable intel GPU add <code>i915.preliminary_hw_support=1</code> to <code>GRUB_CMDLINE_LINUX_DEFAULT</code> in the <code>/etc/default/grub</code> file. This should be solved after 4.3 kernel release.</li> 64 | <li>Problems with video scaling in fullscreen mode can be solved by using gl as an output driver (mplayer -vo gl).</li> 65 | </ul> 66 | 67 | <h2 id="wifi">Wifi</h2> 68 | 69 | <p>Since there is no HW based wifi switch (only Fn+F5 combination) and kernel still tries to read it wifi is reported disabled on every boot. 70 | There was a patch for the 17 inch model <a href="http://www.gossamer-threads.com/lists/linux/kernel/2323659">here</a>.</p> 71 | 72 | <h3 id="temporary-solution-1">Temporary solution 1:</h3> 73 | 74 | <ul> 75 | <li><code>sudo systemctl enable rfkill-unblock@wifi.service</code></li> 76 | <li><code>sudo rfkill unblock wifi</code></li> 77 | <li>Works fine with <code>wicd</code></li> 78 | <li>Configuring NetworkManager service to be run after rfkill service should also work</li> 79 | </ul> 80 | 81 | <h3 id="temporary-solution-2">Temporary solution 2:</h3> 82 | 83 | <ul> 84 | <li><code>echo blacklist ideapad_laptop | sudo tee /etc/modprobe.d/blacklist.conf</code> to disable acpi module</li> 85 | </ul> 86 | 87 | <h2 id="audio-clicking">Audio clicking</h2> 88 | 89 | <p>This is caused by suspend-on-idle module in the pulse audio. Instead of disabling the module I decided to just set very long timeout. 90 | To do that append <code>timeout=36000</code> to line <code>load-module module-suspend-on-idle</code> in the <code>/etc/pulse/default.pa</code> configuration file. 91 | And now restart pulse by running <code>pulseaudio --kill</code> and <code>pulseaudio --start</code>.</p> 92 | 93 | <h2 id="hibernate">Hibernate</h2> 94 | 95 | <ul> 96 | <li>Add <code>resume=/dev/my-swap-partition</code> to <code>GRUB_CMDLINE_LINUX_DEFAULT</code> line in <code>/etc/default/grub</code></li> 97 | <li>Run <code>sudo grub-mkconfig -o /boot/grub/grub.cfg</code></li> 98 | <li>Add <code>resume</code> to the list of <code>HOOKS</code> in <code>/etc/mkinitcpio.conf</code> before <code>filesystems</code> but after all <code>block</code>, <code>sata</code> and other hardware related hooks.</li> 99 | <li>Run <code>sudo mkinitcpio -p linux</code></li> 100 | <li>Reboot</li> 101 | <li><code>systemctl hibernate</code> should work now</li> 102 | </ul> 103 | 104 | 105 | 106 | Arch Linux on Macbook Pro 8.2 (17" 2011) 107 | http://blog.gonzih.me/blog/2015/08/08/arch-linux-on-macbook-pro-8-dot-2-17-2011/ 108 | Sat, 08 Aug 2015 00:00:00 +0000 109 | gonzih@gmail.com (Max Gonzih) 110 | http://blog.gonzih.me/blog/2015/08/08/arch-linux-on-macbook-pro-8-dot-2-17-2011/ 111 | <p>Couple of tips from my experience of running Arch on Macbook 8.2.</p> 112 | 113 | <p></p> 114 | 115 | <h2 id="installation">Installation</h2> 116 | 117 | <p><a href="https://wiki.archlinux.org/index.php/MacBook">Arch Wiki page</a> covers installation well enough in my opinion.</p> 118 | 119 | <h3 id="bootloader-via-systemd-boot">Bootloader via systemd-boot</h3> 120 | 121 | <p>Before installing boot loader change /boot partition type to <code>EFI System</code> (<code>ef00</code>).</p> 122 | 123 | <pre><code>mkfs.fat -F32 /dev/sda1 124 | pacman -S dosfstools 125 | mount /dev/sda1 /mnt/boot 126 | arch-chroot /mnt 127 | bootctl --path=/boot instnall 128 | </code></pre> 129 | 130 | <h3 id="bootloader-via-grub">Bootloader via grub</h3> 131 | 132 | <p>You need to have 2 partitions. /boot should be linux partition ext2. /boot/efi should be <code>ef00</code> type partition of vfta32.</p> 133 | 134 | <pre><code>mount /dev/sda1 /mnt/boot 135 | mkdir -p /mnt/boot/efi 136 | mount /dev/sda2 /mnt/boot/efi 137 | modprobe dm-mod 138 | arch-chroot /mnt 139 | grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=arch_grub --recheck --debug 140 | mkdir -p /boot/grub/locale 141 | cp /usr/share/locale/en\@quot/LC_MESSAGES/grub.mo /boot/grub/locale/en.mo 142 | grub-mkconfig -o /boot/grub/grub.cfg 143 | </code></pre> 144 | 145 | <h2 id="use-broadcom-wl-wireless-module-from-aur">Use broadcom-wl wireless module from AUR</h2> 146 | 147 | <p>This is proprietary broadcom driver. Works fine with BCM4331. 148 | Only thing that does not work - hidden SSID.</p> 149 | 150 | <p>OpenSource driver (that is part of the kernel) and reverse engeneered ones (b43) are too unstable in my experience.</p> 151 | 152 | <p>To make sure that correct module is used by hardwrare blacklist every other module and reboot:</p> 153 | 154 | <pre><code>#/etc/modprobe.d/wl.conf 155 | blacklist b43 156 | blacklist b43legacy 157 | blacklist ssb 158 | blacklist bcm43xx 159 | blacklist brcm80211 160 | blacklist brcmfmac 161 | blacklist brcmsmac 162 | blacklist bcma 163 | </code></pre> 164 | 165 | <h2 id="disable-radeon-gpu-for-better-power-consumption">Disable Radeon GPU for better power consumption</h2> 166 | 167 | <p>This will reduce power usage dramatically but also will disable external screen support via display port. 168 | This laptop relies on the external gpu to work with display port.</p> 169 | 170 | <p>Edit <code>/etc/grub.d/00_header</code> and add outb lines in between <code>set gfxmode=${GRUB_GFXMODE}</code> and <code>load video</code>:</p> 171 | 172 | <pre><code>... 173 | set gfxmode=${GRUB_GFXMODE} 174 | outb 0x728 1 175 | outb 0x710 2 176 | outb 0x740 2 177 | outb 0x750 0 178 | load video 179 | ... 180 | </code></pre> 181 | 182 | <p>Generate new grub config:</p> 183 | 184 | <pre><code># grub-mkconfig -o /boot/grub/grub.cfg 185 | </code></pre> 186 | 187 | <p>PS. This post will probably grow over time.</p> 188 | 189 | 190 | 191 | Nvim terminal + ClojureScript and figwheel 192 | http://blog.gonzih.me/blog/2015/06/15/nvim-terminal-plus-clojurescript-and-figwheel/ 193 | Mon, 15 Jun 2015 00:00:00 +0000 194 | gonzih@gmail.com (Max Gonzih) 195 | http://blog.gonzih.me/blog/2015/06/15/nvim-terminal-plus-clojurescript-and-figwheel/ 196 | <p>This is a small post on how to improve your ClojureScript development flow in NeoVim using its terminal feature.</p> 197 | 198 | <p></p> 199 | 200 | <p><a href="http://neovim.io/">NeoVim</a> nowadays is almost fully compatible with vim. 201 | It is able to reuse my <a href="https://github.com/Gonzih/.vim/blob/master/vimrc">.vimrc</a> file without any issues. 202 | And recently it got proper terminal emulator built-in. 203 | So how about reusing it for ClojureScript development?</p> 204 | 205 | <p>If you are lazy (like I am) and don&rsquo;t want to setup piggieback support 206 | for fireplace.vim or don&rsquo;t feel like tmux integration is good enough 207 | this solution should be your new starting point.</p> 208 | 209 | <p>Just open some clojure[script] file and create second split. 210 | Open terminal using <code>:terminal</code> command.</p> 211 | 212 | <p>Here are some keybindings that will help you to send code from 213 | your clojure buffer to the terminal buffer.</p> 214 | 215 | <pre><code class="language-vim">if has(&quot;nvim&quot;) 216 | &quot; Open terminal and run lein figwheel 217 | nmap &lt;Leader&gt;term &lt;C-w&gt;v:terminal&lt;CR&gt;lein figwheel&lt;CR&gt;&lt;C-\&gt;&lt;C-n&gt;&lt;C-w&gt;p 218 | &quot; Evaluate anything from the visual mode in the next window 219 | vmap &lt;buffer&gt; ,e y&lt;C-w&gt;wpi&lt;CR&gt;&lt;C-\&gt;&lt;C-n&gt;&lt;C-w&gt;p 220 | &quot; Evaluate outer most form 221 | nmap &lt;buffer&gt; ,e ^v%,e 222 | &quot; Evaluate buffer&quot; 223 | nmap &lt;buffer&gt; ,b ggVG,e 224 | endif 225 | </code></pre> 226 | 227 | <p>Hey! Now you can finally stop looking at the emacs land!</p> 228 | 229 | 230 | 231 | ServerSide rendering of Reagent components 232 | http://blog.gonzih.me/blog/2015/02/16/serverside-rendering-of-reagent-components/ 233 | Mon, 16 Feb 2015 00:00:00 +0000 234 | gonzih@gmail.com (Max Gonzih) 235 | http://blog.gonzih.me/blog/2015/02/16/serverside-rendering-of-reagent-components/ 236 | <p>Great thing about React is that you can write what people nowadays call &ldquo;isomorphic JavaScript&rdquo;. 237 | In this post we will not discuss how wrong this term is in many ways, 238 | but instead we will focus on how to achieve similar results in your ClojureScript code using Reagent library.</p> 239 | 240 | <p></p> 241 | 242 | <p>In my experience simplest optimization to run in some js engine on server side is whitespace. 243 | It does not do any renaming/restructuring of your code but eliminates need to take care of dependencies loading. 244 | So our compiler configuration should look something like that:</p> 245 | 246 | <pre><code class="language-clojure">{:id &quot;server-side&quot; 247 | :source-paths [&quot;src&quot;] 248 | :compiler {:output-to &quot;resources/public/javascripts/server-side.js&quot; 249 | :output-dir &quot;resources/public/javascripts/out-server-side&quot; 250 | :preamble [&quot;meta-inf/resources/webjars/react/0.12.1/react-with-addons.min.js&quot;] 251 | :pretty-print false 252 | :warnings true 253 | :optimizations :whitespace}} 254 | </code></pre> 255 | 256 | <p>Next step is to make sure that all functions that use browser specific stuff like document/window are moved in to react lifecycle methods:</p> 257 | 258 | <pre><code class="language-clojure">(def main-component 259 | (with-meta 260 | (fn [] ...) 261 | {:component-did-mount (comp init-my-scroll-handler! 262 | also-init-my-go-loop!)})) 263 | </code></pre> 264 | 265 | <p>Next let&rsquo;s create function that will do some rendering to the string. 266 | I like to keep this function in a component specific ns just for convenience.</p> 267 | 268 | <pre><code class="language-clojure">(def ^:export render-me-to-s [initial-state] 269 | (reset! my-main-state (js-&gt;clj initial-state)) 270 | ; Render component to markup without reactid 271 | (reagent.core/render-to-static-markup [main-component]) 272 | ; Or render component to ready to-go react markup 273 | (reagent.core/render-to-string [main-component])) 274 | </code></pre> 275 | 276 | <p>Now server side bootstrapping, most of this code was taken from <a href="https://github.com/reactjs/react-rails">react-rails plugin</a>.</p> 277 | 278 | <p>First of all react expects to have global or window objects in your js engine (setup.js):</p> 279 | 280 | <pre><code class="language-javascript">var global = global || this; 281 | var self = self || this; 282 | var window = window || this; 283 | var console = global.console || {}; 284 | ['error', 'log', 'info', 'warn'].forEach(function (fn) { 285 | if (!(fn in console)) { 286 | console[fn] = function () {}; 287 | } 288 | }); 289 | </code></pre> 290 | 291 | <p>Now let&rsquo;s try and use all this in our code (for now in Ruby):</p> 292 | 293 | <pre><code class="language-ruby">cxt = V8::Context.new 294 | cxt.load('setup.js') 295 | cxt.load('resources/public/javascripts/server-side.js') 296 | html = cxt.eval(&quot;my.amazing_component.ns.render_me_to_s(#{init_state.to_json})&quot;) 297 | </code></pre> 298 | 299 | <p>And that&rsquo;s it. As a way to pass data from ruby to clojurescript json works fine. 300 | Sometimes you might want to use <code>ActionController::Base.helpers.j</code> helper that will 301 | escape your data for usage inside json, but most of the time you should be alright without it.</p> 302 | 303 | <p>If you have issues with core.async there are 2 ways to solve it. 304 | I personally prefer to move core.async initialization into some lifecycle method. 305 | Another solution is to implement setTimeout function like that in your <code>setup.js</code> snippet:</p> 306 | 307 | <pre><code class="language-javascript">goog.global.setTimeout = function(cb, t) { 308 | cb(); 309 | } 310 | </code></pre> 311 | 312 | <p>Now frontend part. First let&rsquo;s in-line generated html in to the container:</p> 313 | 314 | <pre><code class="language-erb">&lt;div id=&quot;content&quot;&gt;&lt;%= html %&gt;&lt;/div&gt; 315 | </code></pre> 316 | 317 | <p>Then let&rsquo;s write function that will render our component on frontend:</p> 318 | 319 | <pre><code class="language-clojure">(def ^:export mount-me [initial-state] 320 | (reset! my-main-state (js-&gt;clj initial-state)) 321 | (reagent.core/render [main-component] 322 | (js/document.getElementById &quot;content&quot;))) 323 | </code></pre> 324 | 325 | <p>As far as I understand react should reuse your markup on frontend and just attach new handlers to it. 326 | Am I wrong on this one? Don&rsquo;t know yet.</p> 327 | 328 | <p>Inline javascript that you should use on frontend looks like that:</p> 329 | 330 | <pre><code class="language-erb">&lt;script&gt; 331 | my.amazing_component.ns.mount_me(&lt;%= init_state.to_json %&gt;) 332 | &lt;/script&gt; 333 | </code></pre> 334 | 335 | <h3 id="nashorn-example-result-of-my-experiments-in-the-repl">Nashorn example (result of my experiments in the REPL)</h3> 336 | 337 | <pre><code class="language-clojure">(import '[javax.script ScriptEngineManager]) 338 | (def nashorn (.getEngineByName (ScriptEngineManager.) &quot;nashorn&quot;)) 339 | 340 | ; Same as in ruby version 341 | (def setup-script (slurp &quot;setup.js&quot;)) 342 | (def ss-script (slurp &quot;resources/public/javascripts/server-side.js&quot;)) 343 | (def render-script (str &quot;my.amazing_component.ns.render_me_to_s(&quot; my-state-json-string &quot;);&quot;)) 344 | 345 | (.eval nashorn setup-script) 346 | (.eval nashorn ss-script) 347 | (.eval nashorn render-script) ; our html markup 348 | </code></pre> 349 | 350 | <p>I must admit that this code works on small reagent example. 351 | I&rsquo;m unable to load production code from my current project in to Nashorn.</p> 352 | 353 | <p>Also it helps a lot if you started developing your project with server side rendering in mind.</p> 354 | 355 | <p>Of course it&rsquo;s better to have some kind of &ldquo;renderers pool&rdquo; in JVM. 356 | Good thing that clojure allows you to implement thing like that in few lines of code. 357 | In ruby it&rsquo;s not a problem since we have 1 context per worker.</p> 358 | 359 | <p><strong>Useful Links:</strong></p> 360 | 361 | <ul> 362 | <li><a href="https://groups.google.com/forum/#!topic/clojurescript/IIjUxnl4Zbw">ClojureScript mailing list topic</a></li> 363 | </ul> 364 | 365 | 366 | 367 | ln -sf /usr/bin/emacs /usr/bin/vim ? 368 | http://blog.gonzih.me/blog/2015/02/15/ln--sf-/usr/bin/emacs-/usr/bin/vim-/ 369 | Sun, 15 Feb 2015 00:00:00 +0000 370 | gonzih@gmail.com (Max Gonzih) 371 | http://blog.gonzih.me/blog/2015/02/15/ln--sf-/usr/bin/emacs-/usr/bin/vim-/ 372 | <p>Some time ago I actually did run <code>ln -sf /usr/bin/emacs /usr/bin/vim</code>. 373 | And left it like that for a couple of days. 374 | I must say that it was surprisingly nice experience. 375 | Tinkering around with elisp, building editing environment from scratch.</p> 376 | 377 | <p>Of course interesting question is &ldquo;How did I end up with this idea in my head?&rdquo;.</p> 378 | 379 | <p></p> 380 | 381 | <p>I tried to play with emacs few times couple of years ago. 382 | But as a modal editing kind of guy I was unable to comprehend finger bending experience that default key bindings in emacs give you.</p> 383 | 384 | <p>I tried evil mode, but it went not so well. 385 | Problem is that I&rsquo;m also using programmer dvorak layout, so I need to remap couple of keys for better comfort. 386 | I failed all my previous attempts because it was not very trivial at that time to remap those keys everywhere. 387 | Also probably my lack of patience played against me. 388 | So I gave up and continued using vim (something like 5 years of hapiness).</p> 389 | 390 | <p>Recently ClojureScript tool called <a href="https://github.com/bhauman/lein-figwheel">figwheel</a> added repl support. 391 | So as a result you have repl that compiles your clojure code into javascript and executes result in your browser session printing result back to you. 392 | Development flow like that is very common practice in clojure world and one reason why it makes clojure much better. 393 | Problem here is that it does not support nrepl (network repl) protocol and best tool for clojure in vim <a href="https://github.com/tpope/vim-fireplace">vim-fireplace</a> relies on nrepl. 394 | I was using for some time <a href="https://github.com/sjl/tslime2.vim">tslime2</a> in vim to work with ClojureScript. 395 | Idea is very simple - tslime allows you to send pieces of text from your vim into some tmux panel. 396 | It works. You don&rsquo;t have out of the box tooling that will select your top most clojure form sadly. 397 | Once upon a time I had discussion on #clojurescript irc and <a href="https://twitter.com/martinklepsch">@martinklepsh</a> mentioned that nowadays evil-mode is much better. 398 | I was bored and followed that track. I did run <code>rm -rf .emacs.d</code> and started from scratch. 399 | As a result my workflow from vim was ported to emacs in a couple of hours. 400 | It surprised me both how simple was that and how relatively close my setup is to default one in vim/evil. 401 | I set my default editor to emacs and continued doing my thing for a couple of days.</p> 402 | 403 | <p>And here what I think so far.</p> 404 | 405 | <p>Good:</p> 406 | 407 | <ul> 408 | <li>Evil mode is good and it&rsquo;s very close. It&rsquo;s probably closest thing to vim that I ever tried. 409 | It&rsquo;s not fancy smart like vim mode in IntelliJ IDEA that actually reads your .vimrc and uses it to configure keybindings 410 | (Big shout outs to the author of idea plugin. It&rsquo;s very impressive.)</li> 411 | <li>Writing configuration in a language that I understand was a big relief.</li> 412 | <li>Mapping configuration is simpler. You are mapping elisp function to the key. It&rsquo;s simpler and easier to understand than remapping mechanism in vim.</li> 413 | <li>Helm surprised me in it&rsquo;s speed and functionality, I spent lot of time fighting with Ctrl-P/Unite in vim. Configuring Unite was painful experience. Helm on the other hand just works. And works well.</li> 414 | <li>Inferior lisp is so goooood. Having editor with lisp support in mind is incredible experience for any lisp developer. <a href="https://github in inferior lisp mode tuned to play well with clojure">inf-clojure</a> is enhanced clojure mode for the inf-lisp.</li> 415 | <li>It can do async stuff! If you used vim then you know what I mean. In emacs it&rsquo;s just there. Without required pythor/ruby support enabled during compilation.</li> 416 | <li>Built-in package manager. Just run <code>package-install</code> and it&rsquo;s there. No NeoBundle/Bundle installation needed. No need to mess with git submodules.</li> 417 | <li>Ability to inspect everything at run time helps during configuration. Some key executes something weird? Just run <code>describe-key</code> and see what is going on. You can do something similar in vim, but in emacs it&rsquo;s much better.</li> 418 | <li>Startup time is slow and it&rsquo;s solvable. Just run systemd user service with <code>emacs --daemon</code> and that is enough for most cases.</li> 419 | <li>And you still have emacs operating system at your disposal!</li> 420 | <li>Configuring emacs from scratch made me realize that my vim configuration is really really messy and big. I need probably to think about some changes in there.</li> 421 | </ul> 422 | 423 | <p>Not so good:</p> 424 | 425 | <ul> 426 | <li>Paredit feels different from one that exists in vim. Less strict I guess most of the time and too strict when it&rsquo;s unnecessary. Of course this is related only to my habits and muscle memory.</li> 427 | <li>Evil is slower. 428 | Most of the time it&rsquo;s not a problem. But sometimes I&rsquo;m mashing my keyboard too fast and mess happens. 429 | Good example is replace (<code>r</code>) key. 430 | Press <code>r:</code> too fast and you might end up in vim command line.</li> 431 | <li>Evil is just a plugin. Sometimes you are forced to use default emacs mode in some menus/buffers that don&rsquo;t play well with evil.</li> 432 | <li>Good example of painful evil integration is cider. 433 | It just does not work with evil mode. 434 | Most configuration examples that I was able to find on github related to cider and evil mode where just forcing default emacs mode in cider repl and related buffers. 435 | That was big disappointment for me. I had high hopes for the cider. I&rsquo;m realizing that I don&rsquo;t really need everything that cider provides. 436 | I&rsquo;m very happy with just ability to evaluate code, without even debugger and nice stacktraces. 437 | But cider looked so shiny and cool. And bloated. Seems like author of cider does not use evil mode so my hopes that cider+evil story will improve are low. 438 | After few hours of grinding my teeth over emacs configuration I gave up and decided to rely on <a href="https://github.com/clojure-emacs/inf-clojure">inf-clojure</a>.</li> 439 | </ul> 440 | 441 | <p>Will I continue using emacs? Yes.</p> 442 | 443 | <p>Will it be my default editor? Probably no. 444 | Vim feels more reliable because it provides modal editing experience out of the box. 445 | In emacs it an option. 446 | I&rsquo;m totally fine with giving away all that goodness that emacs provides to have proper editing experience <strong>all the time</strong>. 447 | In every buffer, in every menu. 448 | And I will continue slowly improving my emacs configuration.</p> 449 | 450 | <p>This experiment reminded me how many things are missing from my setup. 451 | It also reminded me that <a href="https://neovim.org">neovim</a> might be next big thing in my tool belt. 452 | I&rsquo;m really hoping to see big movement around neovim once project becomes more or less compatible with current viml based configurations.</p> 453 | 454 | <p><strong>PS</strong></p> 455 | 456 | <ul> 457 | <li><a href="http://juanjoalvarez.net/es/detail/2014/sep/19/vim-emacsevil-chaotic-migration-guide/">Here</a> is very good tutorial to get vim users started in emacs.</li> 458 | <li>My .emacs.d is <a href="https://github.com/Gonzih/.emacs.d">here</a>. I tried to keep it minimalistic.</li> 459 | <li>My .vim is <a href="https://github.com/Gonzih/.vim">here</a>.</li> 460 | <li>If you are looking for the best vim like experience in emacs please take a look at the <a href="https://github.com/syl20bnr/spacemacs">spacemacs project</a>.</li> 461 | <li>Looking for the good color theme in emacs? Take a look at the port of <a href="https://github.com/morhetz/gruvbox">vim&rsquo;s gruvbox theme</a>.</li> 462 | </ul> 463 | 464 | 465 | 466 | Grench binary for OpenSUSE 467 | http://blog.gonzih.me/blog/2014/10/23/grench-binary-for-opensuse/ 468 | Thu, 23 Oct 2014 00:00:00 +0000 469 | gonzih@gmail.com (Max Gonzih) 470 | http://blog.gonzih.me/blog/2014/10/23/grench-binary-for-opensuse/ 471 | <p><a href="http://leiningen.org/grench.html">Grenchman</a> is a small tool that runs leiningen tasks over nrepl.</p> 472 | 473 | <p>Helps with JVM startup time during development. 474 | Build in OCaml. If you are not in to the building binary on your own, 475 | there are pre-build binaries on the <a href="http://leiningen.org/grench.html">official website</a>.</p> 476 | 477 | <p>And here is one missing binary for OpenSUSE (tested on 13.1, fedora binary does not work).</p> 478 | 479 | <p><a href="https://dl.dropboxusercontent.com/u/4109351/grenchman/grench-0.2.0-opensuse">grench-opensuse</a> 480 | [<a href="https://dl.dropboxusercontent.com/u/4109351/grenchman/grench-0.2.0-opensuse.sha1">sha1</a> 481 | | <a href="https://dl.dropboxusercontent.com/u/4109351/grenchman/grench-0.2.0-opensuse.asc">sig</a>] 482 | </p> 483 | 484 | 485 | 486 | Two way data bindings in Reagent 487 | http://blog.gonzih.me/blog/2014/10/22/two-way-data-bindings-in-reagent/ 488 | Wed, 22 Oct 2014 00:00:00 +0000 489 | gonzih@gmail.com (Max Gonzih) 490 | http://blog.gonzih.me/blog/2014/10/22/two-way-data-bindings-in-reagent/ 491 | <p>Small code snippet showing ability to generate 2 way data bindings in Reagent. 492 | Underneath it&rsquo;s all about core.async. 493 | Also provides ability to apply transformation using transducers. 494 | </p> 495 | 496 | <pre><code class="language-clojure"> 497 | (defonce form (atom {})) 498 | 499 | (defn bind-input 500 | &quot;Generat on-change callback, 501 | bind value to form-key of form-atom. 502 | Provides ability to implement transformation using transducers.&quot; 503 | [form-atom form-key xform] 504 | (let [local-chan (chan 1 xform)] 505 | (go-loop [] 506 | (swap! form-atom assoc form-key (&lt;! local-chan)) 507 | (recur)) 508 | (fn [event] 509 | (put! local-chan 510 | (.-value (.-target event)))))) 511 | 512 | (defn bound-input 513 | &quot;Generate input, 514 | create two way data binding 515 | between input value and value under form-key in form-atom. 516 | Provides ability to implement transformation using transducers.&quot; 517 | [attrs form-atom form-key xform] 518 | [:input (merge attrs 519 | {:value (form-key @form-atom) 520 | :on-change (bind-input form-atom form-key xform)})]) 521 | 522 | (defn main-component [] 523 | [:div 524 | [:h3 (:name @form) &quot; value&quot;] 525 | [bound-input {:type :text} form :name (filter #(&gt; 15 (count %)))]]) 526 | 527 | (reagent/render-component [main-component] 528 | (js/document.getElementById &quot;content&quot;)) 529 | </code></pre> 530 | 531 | 532 | 533 | Replacing shell scripts with Clojure+JamVM 534 | http://blog.gonzih.me/blog/2014/09/28/replacing-shell-scripts-with-clojure/ 535 | Sun, 28 Sep 2014 00:00:00 +0000 536 | gonzih@gmail.com (Max Gonzih) 537 | http://blog.gonzih.me/blog/2014/09/28/replacing-shell-scripts-with-clojure/ 538 | <p>We all hate shell scripting. 539 | Scripts are annoyingly hard to debug, test and verify. 540 | Would be lovely, to use some kind of lisp for scripting, right? 541 | To do interactive development with repl in your favorite editor. 542 | To write it in a nice predictable language that you also enjoy. 543 | But sometimes it&rsquo;s impossible to add some external dependencies to the system. 544 | What if you have only JVM to your disposal, will you be able to pull it off only with JVM and clojure.jar?</p> 545 | 546 | <p></p> 547 | 548 | <h1 id="basic-setup">Basic setup</h1> 549 | 550 | <p>First what we will need is to get clojure jar file:</p> 551 | 552 | <pre><code>wget -O /opt/clojure.jar 'http://central.maven.org/maven2/org/clojure/clojure/1.6.0/clojure-1.6.0.jar' 553 | </code></pre> 554 | 555 | <p>Next lets create executable <code>/usr/bin/clojure</code> that will live in <code>/usr/bin</code> (or <code>/opt/bin</code> or <code>/home/youruser/bin</code>):</p> 556 | 557 | <pre><code class="language-bash">#!/bin/sh 558 | 559 | exec java -jar /opt/clojure.jar &quot;$@&quot; 560 | </code></pre> 561 | 562 | <p>And now it&rsquo;s time for our hello world script <code>/opt/test.clj</code>:</p> 563 | 564 | <pre><code class="language-clojure">#!/usr/bin/clojure 565 | 566 | (println &quot;hello world&quot;) 567 | </code></pre> 568 | 569 | <p>Make it executable:</p> 570 | 571 | <pre><code class="language-bash">chmod +x /opt/test.clj 572 | </code></pre> 573 | 574 | <p>And run it:</p> 575 | 576 | <pre><code class="language-bash">$ /opt/test.clj 577 | hello world 578 | </code></pre> 579 | 580 | <p>Yay! But it feels kind of slow:</p> 581 | 582 | <pre><code class="language-bash">time /opt/test.clj 583 | hello world 584 | 585 | real 0m2.684s 586 | user 0m2.239s 587 | sys 0m0.186s 588 | </code></pre> 589 | 590 | <p>2 seconds startup time, not really suitable for scripting, right? 591 | Can we improve that? What if there would be JVM with fast startup and low memory usage.</p> 592 | 593 | <h1 id="introducing-jamvm">Introducing JamVM.</h1> 594 | 595 | <p><em>&ldquo;But&hellip; but you told us that there is only JVM available on production system without ability to add external dependencies.&rdquo;</em></p> 596 | 597 | <p>I lied, sorry.</p> 598 | 599 | <p>Compiling JamVM with OpenJDK support:</p> 600 | 601 | <pre><code class="language-bash"># Fetching required dependencies and source 602 | apt-get -y install openjdk-7-jdk openjdk-7-jre build-essential zlib1g-dev 603 | cd /opt 604 | wget -O jamvm-2.0.0.tar.gz 'http://downloads.sourceforge.net/project/jamvm/jamvm/JamVM%202.0.0/jamvm-2.0.0.tar.gz' 605 | tar -xvzf jamvm-2.0.0.tar.gz 606 | 607 | # Building 608 | cd /opt/jamvm-2.0.0 609 | ./configure --with-java-runtime-library=openjdk7 &amp;&amp; make check &amp;&amp; make &amp;&amp; make install 610 | 611 | # Installing in to the openjdk installation 612 | mkdir /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/amd64/jamvm 613 | cp /usr/local/jamvm/lib/libjvm.so /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/amd64/jamvm/libjvm.so 614 | 615 | # Trying it out 616 | java -jamvm -version 617 | </code></pre> 618 | 619 | <p>JamVM will be installed as separate vm in openjdk, so it will not mess with existing installation. 620 | You will need to use -jamvm option to java command to run it with small overhead vm.</p> 621 | 622 | <p>Let&rsquo;s update our clojure executable <code>/usr/bin/clojure</code>:</p> 623 | 624 | <pre><code class="language-bash">#!/bin/sh 625 | 626 | exec java -jamvm -jar /opt/clojure.jar &quot;$@&quot; 627 | </code></pre> 628 | 629 | <p>Let&rsquo;s try it out:</p> 630 | 631 | <pre><code class="language-bash">time /opt/test.clj 632 | hello world 633 | 634 | real 0m0.866s 635 | user 0m0.764s 636 | sys 0m0.076s 637 | </code></pre> 638 | 639 | <p>Better, right?</p> 640 | 641 | <h2 id="how-slow-is-jamvm-some-benchmarks">How slow is JamVM? Some benchmarks:</h2> 642 | 643 | <pre><code class="language-text">Clojure 1.6 644 | 645 | JamVM: 646 | 647 | (factorial 5000) Avg: 248.65890986500017 648 | (fib 20) Avg: 35.33471996000001 649 | (sort-seq) Avg: 405.7438969800002 650 | 651 | OpenJDK: 652 | 653 | (factorial 5000) Avg: 25.016900630000006 654 | (fib 20) Avg: 0.69957772 655 | (sort-seq) Avg: 11.553695560000001 656 | </code></pre> 657 | 658 | <p>Much slower, but if you think about it 659 | shell scripting most of the time is about executing external commands, 660 | IO and data filtering. Might be as well not so bad. 661 | Also memory usage of JamVM makes it perfect for embedded systems.</p> 662 | 663 | <h2 id="why-not-use-something-like-lein-exec">Why not use something like lein exec?</h2> 664 | 665 | <p>Lein exec is nice. But it adds overhead. 666 | If you need external dependencies you can solve it (in theory) 667 | with classpath manipulations in java command (<code>java -cp dep.jar:dep2.jar:.</code>). 668 | Still you can plug lein exec to JamVM if you want.</p> 669 | 670 | <h3 id="update">Update</h3> 671 | 672 | <p>I just noticed that in Ubuntu 14:04 repos there is already JamVM package, 673 | so you can just run <code>apt-get -y install icedtea-7-jre-jamvm</code> to install latest build.</p> 674 | 675 | 676 | 677 | Building Hacker News json api with Haskell 678 | http://blog.gonzih.me/blog/2014/08/13/building-hacker-news-json-api-with-haskell/ 679 | Wed, 13 Aug 2014 00:00:00 +0000 680 | gonzih@gmail.com (Max Gonzih) 681 | http://blog.gonzih.me/blog/2014/08/13/building-hacker-news-json-api-with-haskell/ 682 | <p>Small announcement post.</p> 683 | 684 | <p>Today I launched tiny scotty server that serves json for Hacker News front page. 685 | Project source code is located on <a href="https://github.com/Gonzih/HNApi">github</a>. 686 | You can access api <a href="http://hn.gonzih.me/">here</a>.</p> 687 | 688 | <p>Hacker news parser is implemented using <a href="http://hackage.haskell.org/package/hxt-8.5.2">HXT</a> 689 | and <a href="http://egonschiele.github.io/HandsomeSoup/">HandsomeSoup</a>. 690 | Json is served with help of <a href="https://github.com/scotty-web/scotty/">Scotty</a> web framework. 691 | Currently it&rsquo;s running on Heroku using <a href="https://github.com/begriffs/heroku-buildpack-ghc">this ghc-7.8 buildpack</a>.</p> 692 | 693 | <p>I don&rsquo;t use RSS since I&rsquo;m also interested in points and amount of comments. 694 | For me HN is more about interesting links than community and conversations behind the posts. 695 | So I don&rsquo;t really care about anything except front page.</p> 696 | 697 | <p>Originally I started this project as a Haskell learning exercise. 698 | I hope someone will find it useful. 699 | </p> 700 | 701 | 702 | 703 | Installing Spotify Linux beta on OpenSUSE 13.1 704 | http://blog.gonzih.me/blog/2014/05/27/installing-spotify-linux-beta-on-opensuse-13-dot-1/ 705 | Tue, 27 May 2014 00:00:00 +0000 706 | gonzih@gmail.com (Max Gonzih) 707 | http://blog.gonzih.me/blog/2014/05/27/installing-spotify-linux-beta-on-opensuse-13-dot-1/ 708 | <p>Very small post (more like insruction for myself for the future) on how to convert deb packages provided by Spotify to rpm. 709 | Solution can be applied to any rpm based system (I think so). 710 | </p> 711 | 712 | <ul> 713 | <li>Install <a href="http://software.opensuse.org/package/alien">alien</a> (perl scripts for converting packages).</li> 714 | <li>Download deb from <a href="http://repository.spotify.com/pool/non-free/s/spotify/">spotify repository</a>, pay attention to architecture.</li> 715 | <li>Convert deb -&gt; rpm by running <code>sudo alien --scripts -r spotify*.deb</code>.</li> 716 | <li>Install rpm <code>sudo zypper in spotify*.rpm</code>.</li> 717 | <li>Run <code>spotify</code>.</li> 718 | </ul> 719 | 720 | <p>And it should work. 721 | I noticed few warning about default locates and missing libraries, but client still works fine without any changes to system (symlinks, locales and etc).</p> 722 | 723 | <p><strong>PS</strong></p> 724 | 725 | <p>If you have issues with volume being too loud by default make sure that you changed <code>flat-volumes</code> from <code>yes</code> to <code>no</code> in <code>/etc/pulse/daemon.conf</code></p> 726 | 727 | <p><strong>PPS</strong></p> 728 | 729 | <p>People reported that installing version 0.9 -&gt; uninstalling it -&gt; installing version 1.0 might break your system. So watch out for the uninstallation errors.</p> 730 | 731 | 732 | 733 | Autoconnect to NetworkManager VPN on systemd based system 734 | http://blog.gonzih.me/blog/2014/05/26/autoconnect-to-networkmanager-vpn-on-systemd-based-system/ 735 | Mon, 26 May 2014 00:00:00 +0000 736 | gonzih@gmail.com (Max Gonzih) 737 | http://blog.gonzih.me/blog/2014/05/26/autoconnect-to-networkmanager-vpn-on-systemd-based-system/ 738 | <p>Here is small post with instructions how to setup auto connect to VPN. 739 | Of course you can probably figure out all this yourself, but what if you are lazy?</p> 740 | 741 | <p></p> 742 | 743 | <p>Following was tested on OpenSuse 13.1.</p> 744 | 745 | <ul> 746 | <li><p>First create new VPN connection in NetworkManager.</p></li> 747 | 748 | <li><p>Create dispatcher file that will connect your VPN.</p></li> 749 | </ul> 750 | 751 | <p><code>/etc/NetworkManager/dispatcher.d/vpn-up</code>:</p> 752 | 753 | <pre><code class="language-text">#!/bin/sh 754 | 755 | CONN=&quot;MY-CONNECTION-NAME&quot; 756 | 757 | nmcli con status id $CONN &gt; /dev/null 758 | 759 | rc=$? 760 | if [[ $rc != 0 ]] ; then 761 | nmcli con up id $CONN 762 | sleep 5 # optional wait time 763 | fi 764 | 765 | </code></pre> 766 | 767 | <ul> 768 | <li>Make file executable <code>chmod +x /etc/NetworkManager/dispatcher.d/vpn-up</code></li> 769 | <li>Make sure that dispatcher is running by running <code>journalctl -b -u NetworkManager</code> and looking for the line that looks like:</li> 770 | </ul> 771 | 772 | <p><code>NetworkManager</code>: 773 | ```text journalctl -b -u 774 | <warn> Dispatcher failed: (32) Unit dbus-org.freedesktop.nm-dispatcher.service failed to load: No such file or directory.</p> 775 | 776 | <pre><code> 777 | * If you see line about dispatcher service - enable it manually by running `systemctl enable NetworkManager-dispatcher.service`. 778 | * By default NetworkManager will store VPN password in keyring, to start VPN without keyring dependencies update VPN connection configuration with following changes: 779 | 780 | `/etc/NetworkManager/system-connections/MY-CONNECTION-NAME`: 781 | ```text 782 | ... 783 | [vpn] 784 | password-flags=0 785 | ... 786 | [vpn-secrets] 787 | password=MY-VPN-PASSWORD 788 | 789 | </code></pre> 790 | 791 | <p>And now everything should work like a charm!</p> 792 | 793 | 794 | 795 | HN Tray icon in 50 lines of Clojure 796 | http://blog.gonzih.me/blog/2014/05/24/hn-tray-icon-in-50-lines-of-clojure/ 797 | Sat, 24 May 2014 00:00:00 +0000 798 | gonzih@gmail.com (Max Gonzih) 799 | http://blog.gonzih.me/blog/2014/05/24/hn-tray-icon-in-50-lines-of-clojure/ 800 | <p>Today I got a little bit bored and spend few hours poking around with java.awt in Clojure. 801 | Result is tray app that shows stories from HN front page. 802 | Just in ~50 lines of Clojure code. 803 | Enjoy!</p> 804 | 805 | <p><a href="https://github.com/Gonzih/hn-tray.clj">Github Project.</a></p> 806 | 807 | <p>Source code is below:</p> 808 | 809 | <p></p> 810 | 811 | <pre><code class="language-clojure">(ns hn.core 812 | (:require [cheshire.core :as json] 813 | [clojure.java.browse :refer [browse-url]] 814 | [clojure.java.io :refer [resource]]) 815 | (:import [java.awt SystemTray TrayIcon PopupMenu MenuItem Toolkit] 816 | [java.awt.event ActionListener]) 817 | (:gen-class)) 818 | 819 | (defn menu-item [label callback] 820 | (let [menu (MenuItem. label) 821 | listener (proxy [ActionListener] [] 822 | (actionPerformed [event] (callback)))] 823 | (.addActionListener menu listener) 824 | menu)) 825 | 826 | (def hn-api-url &quot;http://api.ihackernews.com/page&quot;) 827 | 828 | (defn hn-items [] 829 | (-&gt; hn-api-url 830 | slurp 831 | (json/parse-string true) 832 | :items 833 | (#(sort-by :points %)) 834 | reverse)) 835 | 836 | (defn add-hn-to-menu! [menu] 837 | (letfn [(mapfn [{:keys [title url commentCount points]}] 838 | (let [full-title (format &quot;%-4s (%-4s) - %s&quot; points commentCount title) 839 | menu-item (menu-item full-title #(browse-url url))] 840 | (println full-title) 841 | (.add menu menu-item)))] 842 | (doall (map mapfn (hn-items))))) 843 | 844 | (defn exit [] 845 | (shutdown-agents) 846 | (System/exit 0)) 847 | 848 | (defn -main [&amp; args] 849 | (let [tray (SystemTray/getSystemTray) 850 | image (.getImage (Toolkit/getDefaultToolkit) 851 | (resource &quot;icon.png&quot;)) 852 | icon (TrayIcon. image) 853 | exit (menu-item &quot;Exit&quot; exit)] 854 | (.setImageAutoSize icon true) 855 | (.add tray icon) 856 | (loop [] 857 | (let [popup (PopupMenu.)] 858 | (println &quot;Updating items&quot;) 859 | (add-hn-to-menu! popup) 860 | (.add popup exit) 861 | (.setPopupMenu icon popup) 862 | (Thread/sleep (* 5 60 1000)) 863 | (recur))))) 864 | </code></pre> 865 | 866 | 867 | 868 | Hardware Cut/Copy/Paste with Arduino Leonardo 869 | http://blog.gonzih.me/blog/2014/03/04/hardware-cut/copy/paste-with-arduino-leonardo/ 870 | Tue, 04 Mar 2014 00:00:00 +0000 871 | gonzih@gmail.com (Max Gonzih) 872 | http://blog.gonzih.me/blog/2014/03/04/hardware-cut/copy/paste-with-arduino-leonardo/ 873 | <p>Since I switched to Programmed Dvorak layout default keybindings for different operations started to annoy me sometimes. 874 | I was thinking about hardware cut/copy/paste in apps even before that. But only with Dvorak I realized how useful it can be. 875 | I always wondered why there is no hardware support for that on various keyboard that are out there. And then I saw <a href="http://keyboard.io">keyboard.io</a>. 876 | Project is about hackable ergonomic mechanical keyboards build on top of Teensy/Arduino Micro boards. And I decided to play a little bit with that idea. 877 | Lets start with implementing hardware cut/copy/paste using Leonardo and then lets see how far we can push the idea.</p> 878 | 879 | <p></p> 880 | 881 | <h2 id="emulating-keyboard-on-leonardo">Emulating keyboard on Leonardo.</h2> 882 | 883 | <p>With release of first boards based on ATmega32u4 Keyboard and Mouse libraries were introduced in Arduino IDE. 884 | Those libraries allow you to emulate fully functional mouse and keyboard from your Arduino board using USB connection. For more information take a look at the <a href="http://arduino.cc/en/Reference/MouseKeyboard">documentation</a>.</p> 885 | 886 | <h2 id="arduino-wiring">Arduino wiring.</h2> 887 | 888 | <p>Wiring will be very simple. We will have 3 buttons on pins 2, 3 and 4 with pull down resistors.</p> 889 | 890 | <p><img src="https://dl.dropboxusercontent.com/u/4109351/octopress/hardware-cut-copy-paste/schematics1.png" alt="schematics1" /></p> 891 | 892 | <h2 id="hardware-cut-copy-paste">Hardware Cut/Copy/Paste.</h2> 893 | 894 | <p>So this will be our simplest solution to the my original idea. Here is Arduino sketch:</p> 895 | 896 | <pre><code class="language-cpp">// version 0.0.1 897 | 898 | int cutPin = 2; 899 | int copyPin = 3; 900 | int pastePin = 4; 901 | 902 | void setup() { 903 | pinMode(cutPin, INPUT); 904 | pinMode(copyPin, INPUT); 905 | pinMode(pastePin, INPUT); 906 | Keboard.begin() 907 | } 908 | 909 | void loop() { 910 | if (digitalRead(cutpin) == HIGH) { cut(); } 911 | if (digitalRead(copypin) == HIGH) { copy(); } 912 | if (digitalRead(pastepin) == HIGH) { paste(); } 913 | } 914 | 915 | void pressCtrl() { 916 | Keyboard.press(KEY_LEFT_CTRL); 917 | } 918 | 919 | void pressShift() { 920 | Keyboard.press(KEY_LEFT_SHIFT); 921 | } 922 | 923 | void cut() { 924 | pressCtrl(); 925 | Keyboard.write('x'); 926 | Keyboard.releaseAll(); 927 | } 928 | 929 | void copy() { 930 | pressCtrl(); 931 | Keyboard.write('c'); 932 | Keyboard.releaseAll(); 933 | } 934 | 935 | void paste() { 936 | pressCtrl(); 937 | Keyboard.write('v'); 938 | Keyboard.releaseAll(); 939 | } 940 | </code></pre> 941 | 942 | <p>It works! But&hellip; for example in my terminal I use Ctrl+Shift+C to copy selection. 943 | Of course I can press Shift+Copy combination. But maybe there is a better solution.</p> 944 | 945 | <h2 id="automatic-detection-of-key-combination">Automatic detection of key combination.</h2> 946 | 947 | <p>Idea is simple. We have serial port open on Leonardo and our Linux PC. 948 | When I&rsquo;m pressing copy on Leonardo it will ask through serial port PC about required combination. 949 | On PC there will be running ruby script that will detect currently focused window and look up at the configuration file for 950 | keys combination. If there is no combination will be found or reply from script will be timed out we will use default combination.</p> 951 | 952 | <h2 id="detecting-wm-class-from-ruby-2-0-0">Detecting WM_CLASS from Ruby (2.0.0+).</h2> 953 | 954 | <p>From my experience with Xmonad best method to detect unique window type is by WM_CLASS string from X properties. 955 | Here is Window class for the job:</p> 956 | 957 | <pre><code class="language-ruby">class Window 958 | def self.current 959 | Window.new(`xprop -root`) 960 | end 961 | 962 | def initialize(data) 963 | @root_data = data 964 | end 965 | 966 | def id 967 | matches = @root_data.lines.grep(/_NET_ACTIVE_WINDOW\(WINDOW\)/) 968 | 969 | if matches 970 | match_data = matches.first.match(/_NET_ACTIVE_WINDOW\(WINDOW\):.*#\s(.*)\n/) 971 | match_data[1] 972 | else 973 | raise 'No Window id was found' 974 | end 975 | end 976 | 977 | def wm_class 978 | out = `xprop -id '#{id}'` 979 | matches = out.lines.grep(/WM_CLASS\(STRING\)/) 980 | 981 | if matches 982 | match_data = matches.first.match(/WM_CLASS\(STRING\)[^&quot;]*(&quot;.*&quot;)\n/) 983 | match_data[1].gsub(/&quot;/,'').split(', ') 984 | else 985 | raise 'No Window class was found' 986 | end 987 | end 988 | 989 | def is_a?(class_string) 990 | wm_class.any? { |s| s == class_string } 991 | end 992 | end 993 | </code></pre> 994 | 995 | <p>Usage examples:</p> 996 | 997 | <pre><code class="language-ruby">Window.current.wm_class 998 | =&gt; [&quot;gvim&quot;, &quot;Gvim&quot;] 999 | 1000 | Window.current.is_a?(&quot;gvim&quot;) 1001 | =&gt; true 1002 | </code></pre> 1003 | 1004 | <h2 id="keys-configuration">Keys configuration.</h2> 1005 | 1006 | <p>For now lets implement simplest class for that and store all configuration in constant.</p> 1007 | 1008 | <pre><code class="language-ruby">class Keys 1009 | CONFIG = { 1010 | 'terminology' =&gt; { 1011 | 'copy' =&gt; 'ctrl-shift-c', 1012 | 'cut' =&gt; 'ctrl-shift-c', 1013 | 'paste' =&gt; 'ctrl-shift-v' 1014 | } 1015 | } 1016 | 1017 | def self.[](key) 1018 | CONFIG[key] 1019 | end 1020 | 1021 | def self.for(window) 1022 | window.wm_class.map do |k| 1023 | CONFIG[k] 1024 | end.compact.first 1025 | end 1026 | end 1027 | </code></pre> 1028 | 1029 | <p>Usage:</p> 1030 | 1031 | <pre><code class="language-ruby">Keys['terminology']['copy'] 1032 | =&gt; 'ctrl-shift-c' 1033 | 1034 | # When current window is terminology 1035 | Keys.for(Window.current)['copy'] 1036 | =&gt; 'ctrl-shift-c' 1037 | </code></pre> 1038 | 1039 | <h2 id="communicating-with-arduino-via-serialport">Communicating with Arduino via SerialPort.</h2> 1040 | 1041 | <p>Code below uses sketch described above with redefined copy/paste/cut functions.</p> 1042 | 1043 | <pre><code class="language-cpp">String stringIn; 1044 | // Let's assume than combination aren't longer than 4 keys 1045 | String collectedStrings[4]; 1046 | int counter, inByte, i; 1047 | 1048 | void setup(){ 1049 | Serial.begin(9600); 1050 | counter = 0; 1051 | stringIn = String(&quot;&quot;); 1052 | } 1053 | 1054 | void cut() { 1055 | Serial.println(&quot;cut&quot;); 1056 | } 1057 | 1058 | void copy() { 1059 | Serial.println(&quot;copy&quot;); 1060 | } 1061 | 1062 | void paste() { 1063 | Serial.println(&quot;paste&quot;); 1064 | } 1065 | 1066 | void resetReader() { 1067 | counter = 0 1068 | stringIn = String(&quot;&quot;) 1069 | for (i = 0; i &lt;= 4; i++) { 1070 | collectedStrings[i] = String(&quot;&quot;) 1071 | } 1072 | } 1073 | 1074 | void readLine() { 1075 | while(Serial.available()){ 1076 | inByte = Serial.read(); 1077 | stringIn += inByte; 1078 | 1079 | if (inByte == '-'){ // Handle delimiter 1080 | collectedStrings[counter] = String(stringIn); 1081 | stringIn = String(&quot;&quot;); 1082 | counter = counter + 1; 1083 | } 1084 | 1085 | if(inByte == '\r'){ // end of line 1086 | resetReader(); 1087 | return; 1088 | } 1089 | } 1090 | } 1091 | 1092 | void executeCombination() { 1093 | for(i = 0; i &lt;= 4; i++) { 1094 | pressKey(collectedstrings[i]); 1095 | } 1096 | 1097 | Keyboard.releaseAll(); 1098 | } 1099 | 1100 | void pressKeys(String key) { 1101 | switch(key) { 1102 | case &quot;ctrl&quot;: 1103 | pressCtrl(); 1104 | break; 1105 | case &quot;shift&quot;: 1106 | pressShift(); 1107 | break; 1108 | default: 1109 | char[] arr = key.toCharArray(); 1110 | char k = arr[0]; 1111 | Keyboard.write(k); 1112 | } 1113 | } 1114 | </code></pre> 1115 | 1116 | <p>More information on ruby-serialport is <a href="http://ruby-serialport.rubyforge.org/">here</a>.</p> 1117 | 1118 | <pre><code class="language-ruby">require 'serialport' 1119 | 1120 | class Connection 1121 | def initialize(port: nil) 1122 | unless port 1123 | port = `ls /dev/ttyACM*`.lines.first 1124 | end 1125 | 1126 | @connection = SerialPort.new(port, 9600) 1127 | end 1128 | 1129 | def loop 1130 | loop do 1131 | begin 1132 | action = @connection.readline 1133 | @connection.write(&quot;#{Keys.for(Window.current.wm_class)[action]}\r&quot;) 1134 | rescue Exception =&gt; e 1135 | p e 1136 | end 1137 | end 1138 | end 1139 | end 1140 | </code></pre> 1141 | 1142 | <p>Usage:</p> 1143 | 1144 | <pre><code class="language-ruby">Connection.new.loop # starts infinite loop 1145 | </code></pre> 1146 | 1147 | <p><strong>PS.</strong> This post is more like collection of theoretical pices of code. 1148 | I have no time (and probably enthusiasm) to put all this together (at least right now). 1149 | So this implementation can be broken and inaccurate in many ways. 1150 | Feel free to point out any errors and mistakes and I will fix them.</p> 1151 | 1152 | <!-- 1153 | vim: ts=2:sts=2:sw=2:expandtab 1154 | --> 1155 | 1156 | 1157 | 1158 | VisualRuby gem on OpenSUSE 13.1 1159 | http://blog.gonzih.me/blog/2013/12/13/visualruby-gem-on-opensuse-13-dot-1/ 1160 | Fri, 13 Dec 2013 00:00:00 +0000 1161 | gonzih@gmail.com (Max Gonzih) 1162 | http://blog.gonzih.me/blog/2013/12/13/visualruby-gem-on-opensuse-13-dot-1/ 1163 | <p><img src="https://dl.dropboxusercontent.com/u/4109351/octopress/visualruby/1.png" alt="visualruby" /></p> 1164 | 1165 | <p>I found out about ruby gem called <a href="http://visualruby.net/">visualruby</a> and decided to give it a try. Unfortunately it has a lot of dependencies that were missing on my system. 1166 | So to save my (or maybe your&rsquo;s if you are reading this now) time in the future here are required dependencies for OpenSUSE 13.1. 1167 | I bet you can figure out similar packages for different distribution.</p> 1168 | 1169 | <p></p> 1170 | 1171 | <pre><code class="language-sh">sudo zypper -n in \ 1172 | glib2 glib2-branding-openSUSE glib2-devel \ 1173 | atk-devel cairo-devel pango-devel gdk-pixbuf-devel \ 1174 | gtk2-devel gtksourceview2-devel glade 1175 | </code></pre> 1176 | 1177 | <p>Adjust glade command in settings to glade from glade-gtk2. And now you can run it! :) 1178 | It&rsquo;s not working very well (at least in examples) since most of stuff in suse 13.1 is based on gtk3+. 1179 | But I&rsquo;m still in process of figuring things out.</p> 1180 | 1181 | 1182 | 1183 | Yin-Yang Call/cc puzzle in Ruby 1184 | http://blog.gonzih.me/blog/2013/11/26/yin-yang-call/cc-puzzle-in-ruby/ 1185 | Tue, 26 Nov 2013 00:00:00 +0000 1186 | gonzih@gmail.com (Max Gonzih) 1187 | http://blog.gonzih.me/blog/2013/11/26/yin-yang-call/cc-puzzle-in-ruby/ 1188 | <p>Digging deeper in to the call/cc land I found interesting puzzle called yin-yang.</p> 1189 | 1190 | <p>Here is Scheme implementation:</p> 1191 | 1192 | <pre><code class="language-scheme">(let* ((yin 1193 | ((lambda (cc) (display #\@) cc) (call-with-current-continuation (lambda (c) c)))) 1194 | (yang 1195 | ((lambda (cc) (display #\*) cc) (call-with-current-continuation (lambda (c) c))))) 1196 | (yin yang)) 1197 | </code></pre> 1198 | 1199 | <p>It will print <code>@*@**@***@****@*****@******@...</code> forever.</p> 1200 | 1201 | <p></p> 1202 | 1203 | <p><a href="http://yinwang0.wordpress.com/2012/07/27/yin-yang-puzzle/">Here</a> you can find good explanation, 1204 | also few of explanations can be found on <a href="http://stackoverflow.com/questions/2694679/how-does-the-yin-yang-puzzle-work">StackOverflow</a>.</p> 1205 | 1206 | <p>After I understand how it works I got all that excited and implemented given puzzle in ruby:</p> 1207 | 1208 | <pre><code class="language-ruby">require &quot;continuation&quot; 1209 | 1210 | yin = -&gt;(cc) { print &quot;@&quot;; cc }.call(callcc { |c| c }) 1211 | yang = -&gt;(cc) { print &quot;*&quot;; cc }.call(callcc { |c| c }) 1212 | 1213 | yin.call(yang) 1214 | </code></pre> 1215 | 1216 | <p>And it doesn&rsquo;t work. It prints <code>@*@*********...</code> forever.</p> 1217 | 1218 | <p>No idea why. Maybe there are some limitations of <a href="http://www.ruby-doc.org/core-2.0.0/Continuation.html">ruby&rsquo;s call/cc</a>. 1219 | I will research further, but if you have any information about that feel free to comment or email me.</p> 1220 | 1221 | <p>Cheers!</p> 1222 | 1223 | <p><strong>UPDATE</strong> Abinoam Praxedes Marques Junio <a href="https://www.ruby-forum.com/topic/4418860#1129811">figured</a> out that let (which is basically lambda application underneath) is crucial here. 1224 | So here is his fixed version:</p> 1225 | 1226 | <pre><code class="language-ruby">require &quot;continuation&quot; 1227 | 1228 | lambda do |yin, yang| 1229 | yin.call(yang) 1230 | end.call(lambda { |cc| print &quot;@&quot;; cc }.call(callcc { |c| c }), 1231 | lambda { |cc| print &quot;*&quot;; cc }.call(callcc { |c| c })) 1232 | </code></pre> 1233 | 1234 | 1235 | 1236 | 1237 | --------------------------------------------------------------------------------