├── .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 [](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’t want to setup piggieback support
206 | for fireplace.vim or don’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("nvim")
216 | " Open terminal and run lein figwheel
217 | nmap <Leader>term <C-w>v:terminal<CR>lein figwheel<CR><C-\><C-n><C-w>p
218 | " Evaluate anything from the visual mode in the next window
219 | vmap <buffer> ,e y<C-w>wpi<CR><C-\><C-n><C-w>p
220 | " Evaluate outer most form
221 | nmap <buffer> ,e ^v%,e
222 | " Evaluate buffer"
223 | nmap <buffer> ,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 “isomorphic JavaScript”.
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 "server-side"
247 | :source-paths ["src"]
248 | :compiler {:output-to "resources/public/javascripts/server-side.js"
249 | :output-dir "resources/public/javascripts/out-server-side"
250 | :preamble ["meta-inf/resources/webjars/react/0.12.1/react-with-addons.min.js"]
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’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->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’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("my.amazing_component.ns.render_me_to_s(#{init_state.to_json})")
297 | </code></pre>
298 |
299 | <p>And that’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’s in-line generated html in to the container:</p>
313 |
314 | <pre><code class="language-erb"><div id="content"><%= html %></div>
315 | </code></pre>
316 |
317 | <p>Then let’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->clj initial-state))
321 | (reagent.core/render [main-component]
322 | (js/document.getElementById "content")))
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’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"><script>
331 | my.amazing_component.ns.mount_me(<%= init_state.to_json %>)
332 | </script>
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.) "nashorn"))
339 |
340 | ; Same as in ruby version
341 | (def setup-script (slurp "setup.js"))
342 | (def ss-script (slurp "resources/public/javascripts/server-side.js"))
343 | (def render-script (str "my.amazing_component.ns.render_me_to_s(" my-state-json-string ");"))
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’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’s better to have some kind of “renderers pool” in JVM.
356 | Good thing that clojure allows you to implement thing like that in few lines of code.
357 | In ruby it’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 “How did I end up with this idea in my head?”.</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’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’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’s very close. It’s probably closest thing to vim that I ever tried.
409 | It’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’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’s simpler and easier to understand than remapping mechanism in vim.</li>
413 | <li>Helm surprised me in it’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’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’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’s much better.</li>
418 | <li>Startup time is slow and it’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’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’s not a problem. But sometimes I’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’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’m realizing that I don’t really need everything that cider provides.
436 | I’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’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’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’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’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 | "Generat on-change callback,
501 | bind value to form-key of form-atom.
502 | Provides ability to implement transformation using transducers."
503 | [form-atom form-key xform]
504 | (let [local-chan (chan 1 xform)]
505 | (go-loop []
506 | (swap! form-atom assoc form-key (<! local-chan))
507 | (recur))
508 | (fn [event]
509 | (put! local-chan
510 | (.-value (.-target event))))))
511 |
512 | (defn bound-input
513 | "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."
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) " value"]
525 | [bound-input {:type :text} form :name (filter #(> 15 (count %)))]])
526 |
527 | (reagent/render-component [main-component]
528 | (js/document.getElementById "content"))
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’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 "$@"
560 | </code></pre>
561 |
562 | <p>And now it’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 "hello world")
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>“But… but you told us that there is only JVM available on production system without ability to add external dependencies.”</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 && make check && make && 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’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 "$@"
627 | </code></pre>
628 |
629 | <p>Let’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’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’t use RSS since I’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’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 -> 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 -> uninstalling it -> 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="MY-CONNECTION-NAME"
756 |
757 | nmcli con status id $CONN > /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 "http://api.ihackernews.com/page")
827 |
828 | (defn hn-items []
829 | (-> 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 "%-4s (%-4s) - %s" 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 [& args]
849 | (let [tray (SystemTray/getSystemTray)
850 | image (.getImage (Toolkit/getDefaultToolkit)
851 | (resource "icon.png"))
852 | icon (TrayIcon. image)
853 | exit (menu-item "Exit" exit)]
854 | (.setImageAutoSize icon true)
855 | (.add tray icon)
856 | (loop []
857 | (let [popup (PopupMenu.)]
858 | (println "Updating items")
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… 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’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\)[^"]*(".*")\n/)
983 | match_data[1].gsub(/"/,'').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 | => ["gvim", "Gvim"]
999 |
1000 | Window.current.is_a?("gvim")
1001 | => 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' => {
1011 | 'copy' => 'ctrl-shift-c',
1012 | 'cut' => 'ctrl-shift-c',
1013 | 'paste' => '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 | => 'ctrl-shift-c'
1033 |
1034 | # When current window is terminology
1035 | Keys.for(Window.current)['copy']
1036 | => '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("");
1052 | }
1053 |
1054 | void cut() {
1055 | Serial.println("cut");
1056 | }
1057 |
1058 | void copy() {
1059 | Serial.println("copy");
1060 | }
1061 |
1062 | void paste() {
1063 | Serial.println("paste");
1064 | }
1065 |
1066 | void resetReader() {
1067 | counter = 0
1068 | stringIn = String("")
1069 | for (i = 0; i <= 4; i++) {
1070 | collectedStrings[i] = String("")
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("");
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 <= 4; i++) {
1094 | pressKey(collectedstrings[i]);
1095 | }
1096 |
1097 | Keyboard.releaseAll();
1098 | }
1099 |
1100 | void pressKeys(String key) {
1101 | switch(key) {
1102 | case "ctrl":
1103 | pressCtrl();
1104 | break;
1105 | case "shift":
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("#{Keys.for(Window.current.wm_class)[action]}\r")
1134 | rescue Exception => 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’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’s not working very well (at least in examples) since most of stuff in suse 13.1 is based on gtk3+.
1179 | But I’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 "continuation"
1209 |
1210 | yin = ->(cc) { print "@"; cc }.call(callcc { |c| c })
1211 | yang = ->(cc) { print "*"; cc }.call(callcc { |c| c })
1212 |
1213 | yin.call(yang)
1214 | </code></pre>
1215 |
1216 | <p>And it doesn’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’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 "continuation"
1227 |
1228 | lambda do |yin, yang|
1229 | yin.call(yang)
1230 | end.call(lambda { |cc| print "@"; cc }.call(callcc { |c| c }),
1231 | lambda { |cc| print "*"; cc }.call(callcc { |c| c }))
1232 | </code></pre>
1233 |
1234 |
1235 |
1236 |
1237 |
--------------------------------------------------------------------------------