sfeed_tk - randomcrap - random crap programs of varying quality
 (HTM) git clone git://git.codemadness.org/randomcrap
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       sfeed_tk (10619B)
       ---
            1 #!/usr/bin/wish
            2 package require Tk
            3 
            4 # ttk::style theme names (default, clam, classic, etc)
            5 ttk::style theme use classic
            6 #set scrollbar "ttk::scrollbar"
            7 set scrollbar "scrollbar"
            8 
            9 set fontname TkDefaultFont
           10 #set fontname "monospace"
           11 font create font_item_normal -family $fontname -size 10 -weight normal
           12 font create font_item_bold -family $fontname -size 10 -weight bold
           13 
           14 # global config variables.
           15 set config_onlynew 0
           16 set config_newage ""
           17 set config_plumber "xdg-open"
           18 # global feeds list
           19 set comparetime 0
           20 set feeds {}
           21 set mode_read_stdin 0
           22 
           23 proc parse_sfeed_line {line} {
           24         return [split $line "\t"]
           25 }
           26 
           27 # read items in a sfeed(5) file and parse all the fields.
           28 proc read_sfeed_file {file} {
           29         set total 0
           30         set totalnew 0
           31         set comparetime $::comparetime
           32 
           33         # clear list
           34         .tree_items delete [.tree_items children {}]
           35 
           36         if {$::mode_read_stdin == 1} {
           37                 set f stdin
           38         } else {
           39                 set f [open $file]
           40         }
           41         while {[gets $f line] >= 0} {
           42                 incr total
           43 
           44                 set fields [parse_sfeed_line $line]
           45                 set timestampstr [lindex $fields 0]
           46                 set title [lindex $fields 1]
           47                 set link [lindex $fields 2]
           48                 set content [lindex $fields 3]
           49                 set contenttype [lindex $fields 4]
           50                 set id [lindex $fields 5]
           51                 set author [lindex $fields 6]
           52                 set enclosure [lindex $fields 7]
           53                 set category [lindex $fields 8]
           54 
           55                 # parse timestamp
           56 
           57                 # convert to long long
           58                 set e [scan $timestampstr "%lld" timestamp]
           59 
           60                 if {$e == 1} {
           61                         if {$timestamp >= $comparetime} {
           62                                 incr totalnew
           63                         }
           64                         set date [clock format $timestamp -gmt 0 -format "%Y-%m-%d %H:%M"]
           65                 } else {
           66                         set date ""
           67                 }
           68 
           69                 if {$e == 1 && $timestamp >= $comparetime} {
           70                         set isnew "N"
           71                         set style "bold"
           72                 } else {
           73                         set isnew ""
           74                         set style "normal"
           75                 }
           76                 .tree_items insert {} end -id $total -text "" -tags "item item_${style}" \
           77                         -values [list \
           78                                 $timestampstr $title $link $content $contenttype $id $author $enclosure $category \
           79                                 $isnew $date]
           80         }
           81         if {$::mode_read_stdin == 0} {
           82                 close $f
           83         }
           84 
           85         set feed(path) $file
           86         set feed(name) [file tail $file]
           87         set feed(total) $total
           88         set feed(totalnew) $totalnew
           89 
           90         # return struct serialized
           91         return [array get feed]
           92 }
           93 
           94 # count new items in a sfeed(5) file, but do not parse all the lines.
           95 proc count_sfeed_file {file} {
           96         set total 0
           97         set totalnew 0
           98         set comparetime $::comparetime
           99 
          100         set f [open $file]
          101         while {[gets $f line] >= 0} {
          102                 incr total
          103 
          104                 # get first timestamp field
          105                 set idx [string first "        " $line]
          106                 if {$idx == -1} {
          107                         continue
          108                 }
          109                 set timestampstr [string range $line 0 $idx]
          110                 # convert to long long
          111                 set e [scan $timestampstr "%lld" timestamp]
          112                 if {$e == 1 && $timestamp >= $comparetime} {
          113                         incr totalnew
          114                 }
          115         }
          116         close $f
          117 
          118         set feed(path) $file
          119         set feed(name) [file tail $file]
          120         set feed(total) $total
          121         set feed(totalnew) $totalnew
          122 
          123         # return struct serialized
          124         return [array get feed]
          125 }
          126 
          127 proc getcomparetime {} {
          128         set newage 86400
          129         if { $::config_newage != "" } {
          130                 set e [scan $::config_newage "%lld" l]
          131                 if {$e == 1} {
          132                         set newage $l
          133                 }
          134         }
          135         # get current time
          136         set now [clock seconds]
          137         # 1 day is old news
          138         return [expr {$now - $newage}]
          139 }
          140 
          141 # TODO: set and restore divider position per mode.
          142 proc window_set_mode {win mode} {
          143         if {$mode == "1"} {
          144                 $win configure -orient horizontal
          145                 $win paneconfigure .pw.frame_feed -hide 0
          146         }
          147         if {$mode == "2"} {
          148                 $win configure -orient vertical
          149                 $win paneconfigure .pw.frame_feed -hide 0
          150         }
          151         # set mode monocle
          152         if {$mode == "3"} {
          153                 $win paneconfigure .pw.frame_feed -hide 1
          154         }
          155 }
          156 
          157 proc feeds_update_list {} {
          158         set feeds $::feeds
          159 
          160         # clear list
          161         .tree_feed delete [.tree_feed children {}]
          162 
          163         foreach f $feeds {
          164                 array set feed $f
          165 
          166                 if { ${feed(totalnew)} > 0 } {
          167                         .tree_feed insert {} end -text "" -tags "item item_bold" \
          168                                 -values [list ${feed(name)} ${feed(totalnew)} ${feed(total)} ${feed(path)} ]
          169                 } else {
          170                         if {$::config_onlynew == 0} {
          171                                 .tree_feed insert {} end -text "" -tags "item item_normal" \
          172                                         -values [list ${feed(name)} ${feed(totalnew)} ${feed(total)} ${feed(path)} ]
          173                         }
          174                 }
          175         }
          176 }
          177 
          178 proc global_key_release {key} {
          179         # exit
          180         if {$key == "q"} {
          181                 exit 0
          182         }
          183         # "/": start search/filter (if not enabled already).
          184         # TODO: support ctrl-f also? ctrl-n, etc?
          185         if {$key == "slash"} {
          186                 # TODO
          187         }
          188         # toggle showing feeds with new items only.
          189         if {$key == "t"} {
          190                 if {$::config_onlynew == 0} {
          191                         set ::config_onlynew 1
          192                         feeds_update_list
          193                 } else {
          194                         set ::config_onlynew 0
          195                         feeds_update_list
          196                 }
          197         }
          198         # set mode horizontal, vertical, monocle
          199         if {$key == "1"} {
          200                 window_set_mode .pw $key
          201         }
          202         if {$key == "2"} {
          203                 window_set_mode .pw $key
          204         }
          205         if {$key == "3"} {
          206                 window_set_mode .pw $key
          207         }
          208 }
          209 
          210 # update status bar
          211 proc status_set {text} {
          212         .status configure -state normal
          213 #        .status configure -background "#cccccc"
          214         .status delete 1.0 end
          215         .status insert end $text
          216         .status configure -state disabled
          217 }
          218 
          219 proc main {argc argv} {
          220         # environment variables
          221         if {[info exists ::env(SFEED_PLUMBER)]} {
          222                 set ::config_plumber $::env(SFEED_PLUMBER)
          223         }
          224         if {[info exists ::env(SFEED_NEW_AGE)]} {
          225                 set ::config_newage $::env(SFEED_NEW_AGE)
          226         }
          227         set ::comparetime [getcomparetime]
          228 
          229         # make essential widgets
          230 
          231         # paned window, set default mode to horizontal.
          232         panedwindow .pw -sashwidth 8
          233 
          234         # create treeviews:
          235         # selectmode=browse: select one item at a time.
          236         ttk::treeview .tree_feed -selectmode browse \
          237                 -columns "feedname feednew feedtotal filename" \
          238                 -displaycolumns "feedname feednew feedtotal" -show headings
          239         ttk::treeview .tree_items -selectmode browse \
          240                 -columns "timestamp title link content contenttype id author enclosure category isnew date" \
          241                 -displaycolumns "isnew date title" -show headings
          242 
          243         frame .pw.frame_feed -width 100
          244         frame .pw.frame_items -width 100
          245 
          246         set feedcount 0
          247         set feeds []
          248         set feedcount 0
          249         set feednewcount 0
          250         set totalnew 0
          251         set total 0
          252 
          253         # if no files, read from stdin and hide feeds panel by default (monocle layout).
          254         if {$argc == 0} {
          255                 set lfiles {"/dev/stdin"}
          256                 # read and count sfeed file: stdin cannot be reopened.
          257                 set read_func "read_sfeed_file"
          258                 set ::mode_read_stdin 1
          259                 # set monocle by default.
          260                 window_set_mode .pw "3"
          261         } else {
          262                 set lfiles $argv
          263                 set read_func "count_sfeed_file"
          264                 set ::mode_read_stdin 0
          265                 # set vertical by default.
          266                 window_set_mode .pw "1"
          267         }
          268 
          269         foreach file $lfiles {
          270                 array set feed [$read_func $file]
          271 
          272                 # add feed struct serialized
          273                 lappend feeds [array get feed]
          274                 incr feedcount
          275 
          276                 set linecount $feed(total)
          277                 set filename $feed(name)
          278 
          279                 set total [expr {$total + $feed(total)}]
          280                 set totalnew [expr {$totalnew + $feed(totalnew)}]
          281 
          282                 if {$feed(totalnew) > 0} {
          283                         incr feednewcount
          284                 }
          285         }
          286 
          287         set title "(${totalnew}/${total}) - sfeed_tk"
          288 
          289         wm title . $title
          290 
          291         # UI
          292 
          293         # Make the other widgets.
          294 
          295         .tree_feed column feedname -anchor w -stretch 1
          296         .tree_feed column feednew -width 50 -anchor e -stretch 0
          297         .tree_feed column feedtotal -width 50 -anchor e -stretch 0
          298         .tree_feed heading feedname -text "Feeds (${feednewcount}/${feedcount})" -anchor w
          299         .tree_feed heading feednew -text $totalnew -anchor e
          300         .tree_feed heading feedtotal -text $total -anchor e
          301         .tree_feed configure -padding {0 0 0 0}
          302 
          303         .tree_items column isnew -width 50 -anchor c -stretch 0
          304         .tree_items column date -width 135 -anchor e -stretch 0
          305         .tree_items column title -anchor w -stretch 1
          306         .tree_items heading isnew -text "New?" -anchor c
          307         .tree_items heading date -text "Date" -anchor e
          308         .tree_items heading title -text "Title" -anchor w
          309         .tree_items configure -padding {0 0 0 0}
          310 
          311         # scrollbar for feeds
          312         .tree_feed configure -yscrollcommand {.tree_feed_scroll set}
          313         ::scrollbar .tree_feed_scroll -command {.tree_feed yview}
          314 
          315         # scrollbar for feed items
          316         .tree_items configure -yscrollcommand {.tree_items_scroll set}
          317         ::scrollbar .tree_items_scroll -command {.tree_items yview}
          318 
          319         pack .tree_feed -in .pw.frame_feed -fill both -expand yes -side left
          320         pack .tree_feed_scroll -in .pw.frame_feed -side right -fill y
          321         pack .tree_items -in .pw.frame_items -fill both -expand yes -side left
          322         pack .tree_items_scroll -in .pw.frame_items -side right -fill y
          323 
          324         .pw add .pw.frame_feed -minsize 1
          325         .pw add .pw.frame_items -minsize 1
          326 
          327         # make global
          328         set ::feeds $feeds
          329         feeds_update_list
          330 
          331         proc feeditem_button_1_release {} {
          332         }
          333 
          334         # can only (re)read feeds when not stdin.
          335         proc feeditem_doublebutton_1_release {} {
          336                 if {$::mode_read_stdin == 0} {
          337                         set item [.tree_feed item [.tree_feed selection] -values]
          338                         set file [lindex $item 3]
          339                         read_sfeed_file $file
          340                 }
          341         }
          342 
          343         proc feeditem_button_2_release {} {
          344                 puts stdout [.tree_feed selection]
          345         }
          346 
          347         proc feeditem_button_3_release {} {
          348                 puts stdout [.tree_feed selection]
          349         }
          350 
          351         proc feeditem_key_release {key} {
          352                 if {$key == "Return" } {
          353                         feeditem_doublebutton_1_release
          354                 }
          355         }
          356 
          357         proc item_button_1_release {} {
          358         }
          359 
          360         proc item_doublebutton_1_release {} {
          361                 set item [.tree_items item [.tree_items selection] -values]
          362                 set link [lindex $item 2]
          363                 if {$link != ""} {
          364                         exec $::config_plumber "$link" "2>/dev/null" "&"
          365                 }
          366         }
          367 
          368         proc item_button_2_release {} {
          369                 puts stdout [.tree_items selection]
          370         }
          371 
          372         proc item_button_3_release {} {
          373                 puts stdout [.tree_items selection]
          374         }
          375 
          376         proc item_key_release {key} {
          377                 if {$key == "Return" } {
          378                         item_doublebutton_1_release
          379                 }
          380         }
          381 
          382         proc item_selection_change {} {
          383                 set item [.tree_items item [.tree_items selection] -values]
          384                 set link [lindex $item 2]
          385                 status_set $link
          386         }
          387 
          388         .tree_feed tag bind "item" <Double-1> "feeditem_doublebutton_1_release"
          389         .tree_feed tag bind "item" <ButtonRelease-1> "feeditem_button_1_release"
          390         .tree_feed tag bind "item" <ButtonRelease-2> "feeditem_button_2_release"
          391         .tree_feed tag bind "item" <ButtonRelease-3> "feeditem_button_3_release"
          392         .tree_feed tag bind "item" <KeyRelease> "feeditem_key_release %K"
          393 
          394         .tree_items tag bind "item" <Double-1> "item_doublebutton_1_release"
          395         .tree_items tag bind "item" <ButtonRelease-1> "item_button_1_release"
          396         .tree_items tag bind "item" <ButtonRelease-2> "item_button_2_release"
          397         .tree_items tag bind "item" <ButtonRelease-3> "item_button_3_release"
          398         .tree_items tag bind "item" <KeyRelease> "item_key_release %K"
          399 
          400         .tree_items tag bind "item" <<TreeviewSelect>> "item_selection_change"
          401 
          402         # global key handler.
          403         bind . <Key> "global_key_release %K"
          404 
          405         .tree_feed tag configure item_normal -font font_item_normal
          406         .tree_feed tag configure item_bold -font font_item_bold
          407         .tree_items tag configure item_normal -font font_item_normal
          408         .tree_items tag configure item_bold -font font_item_bold
          409 
          410         # status bar: read-only text field (so text can still be selection and copied).
          411         text .status -height 1 -state disabled
          412 
          413         pack .pw -fill both -expand yes -side top
          414         pack .status -side bottom -fill x
          415 
          416         status_set ""
          417 }
          418 
          419 main $argc $argv