mirror of https://github.com/Calamitous/iris.git
Compare commits
8 Commits
80d2c616ca
...
d907c7a3b3
Author | SHA1 | Date |
---|---|---|
Eric B. Budd | d907c7a3b3 | |
Eric B. Budd | b24bb192a9 | |
Eric B. Budd | 93b64d33a9 | |
Eric B. Budd | 1c124349eb | |
Eric B. Budd | e9164717fd | |
Eric B. Budd | 20f97b28a1 | |
Eric B. Budd | 13546edb82 | |
Eric B. Budd | 79bc357828 |
|
@ -0,0 +1,69 @@
|
|||
# Changelog
|
||||
|
||||
## 1.1.0
|
||||
* Iris now composes messages with $EDITOR instead of using an internal editor
|
||||
* Remove (broken) feature that automatically selects a reply when not provided with a topic ID
|
||||
* Expand and clean up tests
|
||||
* Remove dead code
|
||||
* Move CHANGELOG out of the TODO file and into its own file
|
||||
|
||||
## 1.0.13
|
||||
* Fix reply ordering bug
|
||||
|
||||
## 1.0.12
|
||||
* Add Asara's "mark all read" functionality
|
||||
* Fix(?) bug with handling broken UTF-8 characters
|
||||
* Add feature to read the next unread topic ("next")
|
||||
* Exclude user''s own messages from "unread" count
|
||||
|
||||
## 1.0.11
|
||||
* Speed up the topic listing significantly
|
||||
* Add 'unread' (short form 'u') to only list topics with unread messages
|
||||
* Add 'mark_unread' (short form 'm') to mark topics as read without displaying them
|
||||
* Tweaks to help text
|
||||
* Default main listing to unread topics instead of listing all topics
|
||||
* Updates to the way screen dimensions are calculated
|
||||
* Preliminaary work to support pagination
|
||||
* Change permissions message from error to warning so it only shows in debug mode
|
||||
|
||||
## 1.0.10
|
||||
* ~Fix bug causing system to crash when a user removes read permissions from their directory/iris.messages file~
|
||||
|
||||
## 1.0.9
|
||||
* ~Stop checking domain on user validation~
|
||||
* ~Fix bug causing color overflow when color tags break.~ Special thanks go out to Japanoise (https://github.com/japanoise) for reporting this bug!
|
||||
|
||||
## 1.0.8
|
||||
* ~Fix bug when UID has been deleted from /etc/passwd, but user''s message file still exists~
|
||||
* ~Add debug mode to Iris~
|
||||
* ~Refactor Iris to make it easier to load test files to run with~
|
||||
|
||||
## 1.0.7
|
||||
* ~Fix "unread count" bug~
|
||||
|
||||
## 1.0.6
|
||||
* ~Message deletion~
|
||||
* ~Message editing~
|
||||
* ~Gracefully handle bad message files~
|
||||
* ~Fix topic selection when replying without topic ID~
|
||||
* ~Automatically display topics when opening~
|
||||
* ~Move display headers into frame line~
|
||||
* ~Fix truncated message headers being one character too long in topic list~
|
||||
* ~Status flag fix~
|
||||
* ~Keep order of message on edit~
|
||||
* ~Mark unread topics/topics with unread replies in topics list~
|
||||
* ~Add column headers for topics~
|
||||
* ~Document new features~
|
||||
* ~Keep replies on edited topics~
|
||||
* ~Add unread topic to overall unread count~
|
||||
|
||||
## 1.0.5
|
||||
* ~Make all output WIDTH-aware~
|
||||
* ~Add color~
|
||||
* ~Add full message corpus dump for backup/debugging~
|
||||
* ~Add startup enviro health check~
|
||||
* ~Change listing to show last updated timestamp, instead of thread creation timestamp~
|
||||
* ~Add command-line options to README~
|
||||
* ~Add documentation for color feature~
|
||||
* ~Add command-line options to README~
|
||||
* ~Made message file slightly more human-readable~
|
58
README.md
58
README.md
|
@ -19,7 +19,7 @@ Iris is strictly text-based, requiring no GUI or web servers.
|
|||
|
||||
## Installation
|
||||
|
||||
At its core, Iris is simply a single, executable Ruby script. It has been tested and is known to work with Ruby 2.3.5. No extra gems or libraries are required.
|
||||
At its core, Iris is simply a single, executable Ruby script. It has been tested and is known to work with Ruby 2.3.5 and above. No extra gems or libraries are required.
|
||||
|
||||
Copy or symlink `iris.rb` somewhere the whole server can use it; `/usr/local/bin` is a good candidate:
|
||||
|
||||
|
@ -36,11 +36,11 @@ Iris has a readline interface that can be used to navigate the message corpus.
|
|||
|
||||
```bash
|
||||
%> iris
|
||||
Welcome to Iris v. 1.0.13. Type "help" for a list of commands.; Ctrl-D or 'quit' to leave.
|
||||
Welcome to Iris v. 1.1.0. Type "help" for a list of commands.; Ctrl-D or 'quit' to leave.
|
||||
|
||||
| ID | U | TIMESTAMP | AUTHOR | TITLE
|
||||
| 1 | | 2018-01-24T05:49:53Z | jimmy_foo@ctrl-c.club | Welcome!
|
||||
| 2 | 1 | 2018-01-24T16:13:05Z | jerry_berry@ctrl-c.club | Suggestions for a...
|
||||
| 2 | 1 | 2018-01-24T16:22:05Z | jerry_berry@ctrl-c.club | Suggestions for a...
|
||||
|
||||
jimmy_foo@ctrl-c.club>
|
||||
```
|
||||
|
@ -74,7 +74,7 @@ jimmy_foo@ctrl-c.club> topics
|
|||
|
||||
| ID | U | TIMESTAMP | AUTHOR | TITLE
|
||||
| 1 | | 2018-01-24T05:49:53Z | jimmy_foo@ctrl-c.club | Welcome!
|
||||
| 2 | 1 | 2018-01-24T16:13:05Z | jerry_berry@ctrl-c.club | Suggestions for a...
|
||||
| 2 | 1 | 2018-01-24T16:22:05Z | jerry_berry@ctrl-c.club | Suggestions for a...
|
||||
|
||||
```
|
||||
|
||||
|
@ -95,7 +95,7 @@ This outputs a list of top-level topics that have not been read, or have unread
|
|||
jimmy_foo@ctrl-c.club> unread
|
||||
|
||||
| ID | U | TIMESTAMP | AUTHOR | TITLE
|
||||
| 2 | 1 | 2018-01-24T16:13:05Z | jerry_berry@ctrl-c.club | Suggestions for a...
|
||||
| 2 | 1 | 2018-01-24T16:22:05Z | jerry_berry@ctrl-c.club | Suggestions for a...
|
||||
|
||||
```
|
||||
|
||||
|
@ -112,7 +112,7 @@ jimmy_foo@ctrl-c.club> topics
|
|||
|
||||
| ID | U | TIMESTAMP | AUTHOR | TITLE
|
||||
| 1 | | 2018-01-24T05:49:53Z | jimmy_foo@ctrl-c.club | Welcome!
|
||||
| 2 | 1 | 2018-01-24T16:13:05Z | jerry_berry@ctrl-c.club | Suggestions for a...
|
||||
| 2 | 1 | 2018-01-24T16:22:05Z | jerry_berry@ctrl-c.club | Suggestions for a...
|
||||
|
||||
jimmy_foo@ctrl-c.club> 1
|
||||
*** [1] On 2018-01-24T05:49:53Z, jimmy_foo@ctrl-c.club posted...-----------------
|
||||
|
@ -137,10 +137,10 @@ jimmy_foo@ctrl-c.club> topics
|
|||
|
||||
| ID | U | TIMESTAMP | AUTHOR | TITLE
|
||||
| 1 | | 2018-01-24T05:49:53Z | jimmy_foo@ctrl-c.club | Welcome!
|
||||
| 2 | 1 | 2018-01-24T16:13:05Z | jerry_berry@ctrl-c.club | Suggestions for a...
|
||||
| 2 | 1 | 2018-01-24T16:22:05Z | jerry_berry@ctrl-c.club | Suggestions for a...
|
||||
|
||||
jimmy_foo@ctrl-c.club> next
|
||||
*** [2] On 2018-01-24T16:13:05Z, jerry_berry@ctrl-c.club posted...---------------
|
||||
*** [2] On 2018-01-24T16:22:05Z, jerry_berry@ctrl-c.club posted...---------------
|
||||
Suggestions for a tilde home?
|
||||
|
||||
I'm trying to decide on a new place in the tildeverse to call home. Any ideas?
|
||||
|
@ -159,13 +159,13 @@ I'm trying to decide on a new place in the tildeverse to call home. Any ideas?
|
|||
|
||||
This allows you to add a new top-level topic to the board. The first line of your new topic will be used as the topic title.
|
||||
|
||||
The line editor is quite basic. Enter your message, line-by-line, and type a single period on a line by itself to end the message.
|
||||
Iris will allow you to type in your message in the editor you have defined in your shell with the `$EDITOR` environment variable.
|
||||
|
||||
If you post an empty message, the system will discard it.
|
||||
|
||||
```
|
||||
jimmy_foo@ctrl-c.club~> compose
|
||||
Writing a new topic. Type a period on a line by itself to end message.
|
||||
Writing a new topic.
|
||||
|
||||
new~> How do I spoo the fleem?
|
||||
new~> It's not in the docs and my boss is asking. Any help is appreciated!
|
||||
|
@ -177,8 +177,8 @@ jimmy_foo@ctrl-c.club~> topics
|
|||
|
||||
| ID | U | TIMESTAMP | AUTHOR | TITLE
|
||||
| 1 | | 2018-01-24T05:49:53Z | jimmy_foo@ctrl-c.club | Welcome!
|
||||
| 2 | | 2018-01-24T16:13:05Z | jerry_berry@ctrl-c.club | Suggestions for a...
|
||||
| 3 | 1 | 2018-01-23T00:13:44Z | jimmy_foo@ctrl-c.club | How do I spoo the...
|
||||
| 2 | | 2018-01-24T16:22:05Z | jerry_berry@ctrl-c.club | Suggestions for a...
|
||||
| 3 | 1 | 2018-01-23T00:22:44Z | jimmy_foo@ctrl-c.club | How do I spoo the...
|
||||
```
|
||||
|
||||
---
|
||||
|
@ -188,14 +188,13 @@ jimmy_foo@ctrl-c.club~> topics
|
|||
|
||||
Replies are responses to a specific topic -- they only appear when displaying the topic.
|
||||
|
||||
The line editor is quite basic. Enter your message, line-by-line, and type a single period on a line by itself to end the message.
|
||||
Iris will allow you to type in your message in the editor you have defined in your shell with the `$EDITOR` environment variable.
|
||||
|
||||
If you post an empty message, the system will discard it.
|
||||
|
||||
```
|
||||
jennie_minnie@ctrl-c.club~> reply 3
|
||||
Writing a reply to topic 'How do I spoo the fleem?'.
|
||||
Type a period on a line by itself to end message.
|
||||
|
||||
reply~> Simple, you just boondoggle the flibbertigibbet. That should be in the manual.
|
||||
reply~> .
|
||||
|
@ -203,7 +202,7 @@ Reply saved!
|
|||
|
||||
jennie_minnie@ctrl-c.club~> 3
|
||||
|
||||
*** [3] On 2018-01-23T00:13:44Z, jimmy_foo@ctrl-c.club posted...-----------------
|
||||
*** [3] On 2018-01-23T00:22:44Z, jimmy_foo@ctrl-c.club posted...-----------------
|
||||
How do I spoo the fleem?
|
||||
It's not in the docs and my boss is asking. Any help is appreciated!
|
||||
---------------------------------------------------------------------------------
|
||||
|
@ -229,7 +228,7 @@ A topic ID will always be strictly numeric, "3" in the following example.
|
|||
|
||||
The message or topic ID can be found in square brackets in the informational text above each message.
|
||||
|
||||
The line editor is quite basic. Enter your edited message, line-by-line, and type a single period on a line by itself to end the message.
|
||||
Iris will allow you to type in your message in the editor you have defined in your shell with the `$EDITOR` environment variable.
|
||||
|
||||
If you post an empty message, the system will discard it and the edit will be ignored.
|
||||
|
||||
|
@ -238,7 +237,7 @@ After an edit, a status flag will appear on the message, letting others know the
|
|||
```
|
||||
jennie_minnie@ctrl-c.club~> 3
|
||||
|
||||
*** [3] On 2018-01-23T00:13:44Z, jimmy_foo@ctrl-c.club posted...-----------------
|
||||
*** [3] On 2018-01-23T00:22:44Z, jimmy_foo@ctrl-c.club posted...-----------------
|
||||
How do I spoo the fleem?
|
||||
It's not in the docs and my boss is asking. Any help is appreciated!
|
||||
---------------------------------------------------------------------------------
|
||||
|
@ -250,7 +249,6 @@ It's not in the docs and my boss is asking. Any help is appreciated!
|
|||
|
||||
jennie_minnie@ctrl-c.club~> edit M5
|
||||
Editing message 'Simple, you just boondoggle the flibbertigibbet. That shoul...'
|
||||
Type a period on a line by itself to end message.
|
||||
|
||||
edit~> Simple, you just boondoggle the flibbertigibbet. That's in the manual on page 45.
|
||||
edit~> .
|
||||
|
@ -258,7 +256,7 @@ Message edited!
|
|||
|
||||
jennie_minnie@ctrl-c.club~> 3
|
||||
|
||||
*** [3] On 2018-01-23T00:13:44Z, jimmy_foo@ctrl-c.club posted...-----------------
|
||||
*** [3] On 2018-01-23T00:22:44Z, jimmy_foo@ctrl-c.club posted...-----------------
|
||||
How do I spoo the fleem?
|
||||
It's not in the docs and my boss is asking. Any help is appreciated!
|
||||
---------------------------------------------------------------------------------
|
||||
|
@ -292,7 +290,7 @@ The `undelete` command is provided as a mnemonic convenience; it is identical in
|
|||
```
|
||||
jennie_minnie@ctrl-c.club~> 3
|
||||
|
||||
*** [3] On 2018-01-23T00:13:44Z, jimmy_foo@ctrl-c.club posted...-----------------
|
||||
*** [3] On 2018-01-23T00:22:44Z, jimmy_foo@ctrl-c.club posted...-----------------
|
||||
How do I spoo the fleem?
|
||||
It's not in the docs and my boss is asking. Any help is appreciated!
|
||||
---------------------------------------------------------------------------------
|
||||
|
@ -307,7 +305,7 @@ Deleted message 'Simple, you just boondoggle the flibbertigibbet. That shoul...
|
|||
|
||||
jennie_minnie@ctrl-c.club~> 3
|
||||
|
||||
*** [3] On 2018-01-23T00:13:44Z, jimmy_foo@ctrl-c.club posted...-----------------
|
||||
*** [3] On 2018-01-23T00:22:44Z, jimmy_foo@ctrl-c.club posted...-----------------
|
||||
How do I spoo the fleem?
|
||||
It's not in the docs and my boss is asking. Any help is appreciated!
|
||||
---------------------------------------------------------------------------------
|
||||
|
@ -320,7 +318,7 @@ Undeleted message 'Simple, you just boondoggle the flibbertigibbet. That sho...
|
|||
|
||||
jennie_minnie@ctrl-c.club~> 3
|
||||
|
||||
*** [3] On 2018-01-23T00:13:44Z, jimmy_foo@ctrl-c.club posted...-----------------
|
||||
*** [3] On 2018-01-23T00:22:44Z, jimmy_foo@ctrl-c.club posted...-----------------
|
||||
How do I spoo the fleem?
|
||||
It's not in the docs and my boss is asking. Any help is appreciated!
|
||||
---------------------------------------------------------------------------------
|
||||
|
@ -370,8 +368,8 @@ This outputs the current version of Iris, along with messsage, topic, and author
|
|||
```bash
|
||||
jennie_minnie@ctrl-c.club~> info
|
||||
|
||||
Iris 1.0.13
|
||||
13 topics, 0 unread.
|
||||
Iris 1.1.0
|
||||
22 topics, 0 unread.
|
||||
50 messages, 0 unread.
|
||||
10 authors.
|
||||
```
|
||||
|
@ -404,7 +402,7 @@ iris --version
|
|||
```
|
||||
|
||||
```bash
|
||||
Iris 1.0.13
|
||||
Iris 1.1.0
|
||||
```
|
||||
|
||||
---
|
||||
|
@ -420,8 +418,8 @@ iris --stats
|
|||
```
|
||||
|
||||
```bash
|
||||
Iris 1.0.13
|
||||
13 topics, 0 unread.
|
||||
Iris 1.1.0
|
||||
22 topics, 0 unread.
|
||||
50 messages, 0 unread.
|
||||
10 authors.
|
||||
```
|
||||
|
@ -618,6 +616,12 @@ gem install --user-install minitest
|
|||
gem install --user-install mocha
|
||||
```
|
||||
|
||||
To run the tests:
|
||||
|
||||
```bash
|
||||
ruby tests/iris_test.rb
|
||||
```
|
||||
|
||||
## Technical Bits
|
||||
|
||||
* [Dependencies](#dependencies)
|
||||
|
|
62
TODO.md
62
TODO.md
|
@ -66,65 +66,3 @@
|
|||
* Common message file location for the security-conscious
|
||||
* JSON -> SSI -> Javascript webreader
|
||||
|
||||
# Changelog
|
||||
|
||||
## 1.0.13
|
||||
* Fix reply ordering bug
|
||||
|
||||
## 1.0.12
|
||||
* Add Asara's "mark all read" functionality
|
||||
* Fix(?) bug with handling broken UTF-8 characters
|
||||
* Add feature to read the next unread topic ("next")
|
||||
* Exclude user''s own messages from "unread" count
|
||||
|
||||
## 1.0.11
|
||||
* Speed up the topic listing significantly
|
||||
* Add 'unread' (short form 'u') to only list topics with unread messages
|
||||
* Add 'mark_unread' (short form 'm') to mark topics as read without displaying them
|
||||
* Tweaks to help text
|
||||
* Default main listing to unread topics instead of listing all topics
|
||||
* Updates to the way screen dimensions are calculated
|
||||
* Preliminaary work to support pagination
|
||||
* Change permissions message from error to warning so it only shows in debug mode
|
||||
|
||||
## 1.0.10
|
||||
* ~Fix bug causing system to crash when a user removes read permissions from their directory/iris.messages file~
|
||||
|
||||
## 1.0.9
|
||||
* ~Stop checking domain on user validation~
|
||||
* ~Fix bug causing color overflow when color tags break.~ Special thanks go out to Japanoise (https://github.com/japanoise) for reporting this bug!
|
||||
|
||||
## 1.0.8
|
||||
* ~Fix bug when UID has been deleted from /etc/passwd, but user''s message file still exists~
|
||||
* ~Add debug mode to Iris~
|
||||
* ~Refactor Iris to make it easier to load test files to run with~
|
||||
|
||||
## 1.0.7
|
||||
* ~Fix "unread count" bug~
|
||||
|
||||
## 1.0.6
|
||||
* ~Message deletion~
|
||||
* ~Message editing~
|
||||
* ~Gracefully handle bad message files~
|
||||
* ~Fix topic selection when replying without topic ID~
|
||||
* ~Automatically display topics when opening~
|
||||
* ~Move display headers into frame line~
|
||||
* ~Fix truncated message headers being one character too long in topic list~
|
||||
* ~Status flag fix~
|
||||
* ~Keep order of message on edit~
|
||||
* ~Mark unread topics/topics with unread replies in topics list~
|
||||
* ~Add column headers for topics~
|
||||
* ~Document new features~
|
||||
* ~Keep replies on edited topics~
|
||||
* ~Add unread topic to overall unread count~
|
||||
|
||||
## 1.0.5
|
||||
* ~Make all output WIDTH-aware~
|
||||
* ~Add color~
|
||||
* ~Add full message corpus dump for backup/debugging~
|
||||
* ~Add startup enviro health check~
|
||||
* ~Change listing to show last updated timestamp, instead of thread creation timestamp~
|
||||
* ~Add command-line options to README~
|
||||
* ~Add documentation for color feature~
|
||||
* ~Add command-line options to README~
|
||||
* ~Made message file slightly more human-readable~
|
||||
|
|
249
iris.rb
249
iris.rb
|
@ -4,11 +4,12 @@ require 'digest'
|
|||
require 'etc'
|
||||
require 'json'
|
||||
require 'readline'
|
||||
require 'tempfile'
|
||||
require 'time'
|
||||
# require 'pry' # Only needed for debugging
|
||||
|
||||
class Config
|
||||
VERSION = '1.0.13'
|
||||
VERSION = '1.1.0'
|
||||
MESSAGE_FILE = "#{ENV['HOME']}/.iris.messages"
|
||||
HISTORY_FILE = "#{ENV['HOME']}/.iris.history"
|
||||
IRIS_SCRIPT = __FILE__
|
||||
|
@ -16,26 +17,6 @@ class Config
|
|||
USER = ENV['USER'] || ENV['LOGNAME'] || ENV['USERNAME']
|
||||
HOSTNAME = `hostname -d`.chomp
|
||||
AUTHOR = "#{USER}@#{HOSTNAME}"
|
||||
OPTIONS = %w[
|
||||
--debug
|
||||
--dump
|
||||
--help
|
||||
--interactive
|
||||
--mark-all-read
|
||||
--stats
|
||||
--test-file
|
||||
--version
|
||||
-d
|
||||
-f
|
||||
-h
|
||||
-i
|
||||
-p
|
||||
-s
|
||||
-v
|
||||
]
|
||||
INTERACTIVE_OPTIONS = %w[-i --interactive]
|
||||
NONINTERACTIVE_OPTIONS = %w[-d --dump -h --help -v --version -s --stats --mark-all-read]
|
||||
NONFILE_OPTIONS = %w[-h --help -v --version]
|
||||
|
||||
@@debug_mode = false
|
||||
|
||||
|
@ -51,10 +32,6 @@ class Config
|
|||
"#{messagefile_filename}.read"
|
||||
end
|
||||
|
||||
def self.historyfile_filename
|
||||
"#{messagefile_filename}.history"
|
||||
end
|
||||
|
||||
def self.enable_debug_mode
|
||||
@@debug_mode = true
|
||||
end
|
||||
|
@ -136,8 +113,8 @@ class Corpus
|
|||
@@corpus = Config.find_files.map { |filepath| IrisFile.load_messages(filepath) }.flatten.sort_by(&:timestamp)
|
||||
end
|
||||
|
||||
@@my_corpus = IrisFile.load_messages.sort_by(&:timestamp)
|
||||
@@my_reads = IrisFile.load_reads
|
||||
@@my_corpus = IrisFile.load_messages.sort_by(&:timestamp)
|
||||
@@my_read_hashes = IrisFile.load_reads
|
||||
|
||||
@@unread_messages = nil
|
||||
|
||||
|
@ -156,10 +133,6 @@ class Corpus
|
|||
@@corpus.to_json
|
||||
end
|
||||
|
||||
def self.all
|
||||
@@corpus
|
||||
end
|
||||
|
||||
def self.edited_hashes
|
||||
@@edited_hashes
|
||||
end
|
||||
|
@ -168,6 +141,10 @@ class Corpus
|
|||
@@topics
|
||||
end
|
||||
|
||||
def self.authors
|
||||
@@corpus.map(&:author).uniq.sort
|
||||
end
|
||||
|
||||
def self.mine
|
||||
@@my_corpus
|
||||
end
|
||||
|
@ -176,10 +153,6 @@ class Corpus
|
|||
@@my_corpus.map(&:hash).include? message.hash
|
||||
end
|
||||
|
||||
def self.is_topic?(message)
|
||||
@@topics.map(&:hash).include? message.hash
|
||||
end
|
||||
|
||||
def self.index_of(message)
|
||||
@@corpus.map(&:hash).index message.hash
|
||||
end
|
||||
|
@ -192,25 +165,20 @@ class Corpus
|
|||
return nil unless hash
|
||||
index = @@all_hash_to_index[hash]
|
||||
return nil unless index
|
||||
all[index]
|
||||
end
|
||||
|
||||
def self.has_edit_hash(hash)
|
||||
return nil unless hash
|
||||
Corpus.all.map(&:edit_hash).include?(hash)
|
||||
@@corpus[index]
|
||||
end
|
||||
|
||||
def self.find_all_by_parent_hash(hash)
|
||||
return [] unless hash
|
||||
indexes = @@all_parent_hash_to_index[hash]
|
||||
return [] unless indexes
|
||||
indexes.map{ |idx| all[idx] }.compact.select(&:show_me?)
|
||||
indexes.map{ |idx| @@corpus[idx] }.compact.select(&:show_me?)
|
||||
end
|
||||
|
||||
def self.find_topic_by_id(topic_lookup)
|
||||
return nil unless topic_lookup
|
||||
index = topic_lookup.to_i - 1
|
||||
topics[index] if index >= 0 && index < topics.length
|
||||
@@topics[index] if index >= 0 && index < @@topics.length
|
||||
end
|
||||
|
||||
def self.find_message_by_id(message_lookup)
|
||||
|
@ -224,21 +192,13 @@ class Corpus
|
|||
find_message_by_hash(topic_lookup)
|
||||
end
|
||||
|
||||
def self.read_hashes
|
||||
@@my_reads
|
||||
end
|
||||
|
||||
def self.unread_messages
|
||||
@@unread_messages ||= @@corpus
|
||||
.select { |message| message.show_me? }
|
||||
.reject{ |m| @@my_reads.include? m.hash }
|
||||
.reject{ |m| @@my_read_hashes.include? m.hash }
|
||||
.reject{ |m| @@my_corpus.map(&:hash).include? m.hash }
|
||||
end
|
||||
|
||||
def self.unread_message_hashes
|
||||
self.unread_messages.map(&:hash)
|
||||
end
|
||||
|
||||
def self.unread_topics
|
||||
@@topics.select do |m|
|
||||
# Is the topic unread, or are any of its displayable replies unread?
|
||||
|
@ -252,7 +212,7 @@ class Corpus
|
|||
end
|
||||
|
||||
def self.mark_as_read(hashes)
|
||||
new_reads = (Corpus.read_hashes + hashes).uniq.sort
|
||||
new_reads = (@@my_read_hashes + hashes).uniq.sort
|
||||
IrisFile.write_read_file(new_reads.to_json)
|
||||
Corpus.load
|
||||
end
|
||||
|
@ -367,8 +327,6 @@ class IrisFile
|
|||
end
|
||||
|
||||
class Message
|
||||
FILE_FORMAT = 'v2'
|
||||
|
||||
attr_reader :timestamp, :edit_hash, :author, :parent, :message, :errors, :is_deleted
|
||||
|
||||
def initialize(message, parent = nil, author = Config::AUTHOR, edit_hash = nil, timestamp = Time.now.utc.iso8601, is_deleted = nil)
|
||||
|
@ -435,10 +393,6 @@ class Message
|
|||
@errors.empty?
|
||||
end
|
||||
|
||||
def topic?
|
||||
parent.nil?
|
||||
end
|
||||
|
||||
def replace!
|
||||
new_corpus = Corpus.mine.reject { |message| message.hash == self.hash } << self
|
||||
IrisFile.write_corpus(JSON.pretty_generate(new_corpus))
|
||||
|
@ -533,6 +487,7 @@ class Message
|
|||
[to_display] + replies.map(&:to_display)
|
||||
end
|
||||
|
||||
# TODO: Is this only used for hashing? Maybe rename.
|
||||
def to_json(*args)
|
||||
{
|
||||
hash: hash,
|
||||
|
@ -560,7 +515,7 @@ class Message
|
|||
end
|
||||
|
||||
def topic_id
|
||||
return nil unless Corpus.is_topic?(self)
|
||||
return nil unless self.is_topic?
|
||||
Corpus.topic_index_of(self) + 1
|
||||
end
|
||||
|
||||
|
@ -572,15 +527,15 @@ class Message
|
|||
end
|
||||
|
||||
def leader_text
|
||||
topic? ? "{g ***} [#{topic_id}] #{status_flag}" : ["{g ===}", "[#{id}]", status_flag].compact.join(' ')
|
||||
is_topic? ? "{g ***} [#{topic_id}] #{status_flag}" : ["{g ===}", "[#{id}]", status_flag].compact.join(' ')
|
||||
end
|
||||
|
||||
def verb_text
|
||||
topic? ? 'posted' : 'replied'
|
||||
is_topic? ? 'posted' : 'replied'
|
||||
end
|
||||
|
||||
def indent_text
|
||||
topic? ? '' : ' | '
|
||||
is_topic? ? '' : ' | '
|
||||
end
|
||||
|
||||
def unconfirmed_payload
|
||||
|
@ -600,8 +555,6 @@ class Display
|
|||
WIDTH = [ENV['COLUMNS'].to_i, `tput cols`.chomp.to_i, MIN_WIDTH].compact.max
|
||||
HEIGHT = [ENV['ROWS'].to_i, `tput lines`.chomp.to_i, MIN_HEIGHT].compact.max
|
||||
|
||||
# p Readline.get_screen_size
|
||||
# WIDTH = Readline.get_screen_size[1]
|
||||
TITLE_WIDTH = WIDTH - 26
|
||||
|
||||
def self.permissions_error(filename, file_description, permission_string, mode_string, consequence = nil)
|
||||
|
@ -634,7 +587,7 @@ class Display
|
|||
end
|
||||
|
||||
def self.topic_author_width
|
||||
Corpus.topics.map(&:author).map(&:length).max || 1
|
||||
Corpus.authors.map(&:length).max || 1
|
||||
end
|
||||
|
||||
def self.print_index(index)
|
||||
|
@ -654,7 +607,7 @@ class Display
|
|||
end
|
||||
|
||||
class Interface
|
||||
ONE_SHOTS = %w{ compose delete edit freshen help info mark_all_read mark_read next quit reply reset_display topics unread }
|
||||
ONE_SHOTS = %w{ compose delete edit freshen help info mark_all_read mark_read next quit reset_display topics unread }
|
||||
CMD_MAP = {
|
||||
'?' => 'help',
|
||||
'c' => 'compose',
|
||||
|
@ -687,21 +640,6 @@ class Interface
|
|||
'unread' => 'unread',
|
||||
}
|
||||
|
||||
def browsing_handler(line)
|
||||
tokens = line.split(/\s/)
|
||||
cmd = tokens.first
|
||||
cmd = CMD_MAP[cmd] || cmd
|
||||
return self.send(cmd.to_sym) if ONE_SHOTS.include?(cmd) && tokens.length == 1
|
||||
return show_topic(cmd) if cmd =~ /^\d+$/
|
||||
# If we've gotten this far, we must have args. Let's handle 'em.
|
||||
arg = tokens.last
|
||||
return reply(arg) if cmd == 'reply'
|
||||
return edit(arg) if cmd == 'edit'
|
||||
return delete(arg) if cmd == 'delete'
|
||||
return mark_read(arg) if cmd == 'mark_read'
|
||||
Display.say 'Unrecognized command. Type "help" for a list of available commands.'
|
||||
end
|
||||
|
||||
def reset_display
|
||||
Display.say `tput reset`.chomp
|
||||
end
|
||||
|
@ -711,7 +649,7 @@ class Interface
|
|||
unread_topic_count = Corpus.unread_topics.size
|
||||
message_count = Corpus.size
|
||||
unread_message_count = Corpus.unread_messages.size
|
||||
author_count = Corpus.all.map(&:author).uniq.size
|
||||
author_count = Corpus.authors.size
|
||||
|
||||
Display.flowerbox(
|
||||
"Iris #{Config::VERSION}",
|
||||
|
@ -738,9 +676,16 @@ class Interface
|
|||
end
|
||||
|
||||
def compose
|
||||
@mode = :composing
|
||||
@text_buffer = ''
|
||||
Display.say 'Writing a new topic. Type a period on a line by itself to end message.'
|
||||
Display.say 'Writing a new topic.'
|
||||
|
||||
message_text = external_editor()
|
||||
|
||||
if message_text.length <= 1
|
||||
Display.say 'Empty message, discarding...'
|
||||
else
|
||||
Message.new(message_text).save!
|
||||
Display.say 'Topic saved!'
|
||||
end
|
||||
end
|
||||
|
||||
def next
|
||||
|
@ -752,7 +697,6 @@ class Interface
|
|||
end
|
||||
|
||||
message = Corpus.unread_topics.first
|
||||
@reply_topic = message.hash
|
||||
|
||||
Display.say message.to_topic_display
|
||||
Display.say
|
||||
|
@ -760,25 +704,31 @@ class Interface
|
|||
Corpus.mark_as_read([message.hash] + message.replies.map(&:hash))
|
||||
end
|
||||
|
||||
def reply(topic_id = @reply_topic)
|
||||
def reply(topic_id)
|
||||
unless topic_id
|
||||
Display.say "I can't reply to nothing! Include a topic ID or view a topic to reply to."
|
||||
Display.say "I can't reply to nothing! Include a topic ID to reply to."
|
||||
return
|
||||
end
|
||||
|
||||
if parent = (Corpus.find_topic_by_id(topic_id) || Corpus.find_topic_by_hash(topic_id))
|
||||
@reply_topic = parent.hash
|
||||
reply_topic = parent.hash
|
||||
else
|
||||
Display.say "Could not reply; unable to find a topic with ID '#{topic_id}'"
|
||||
return
|
||||
end
|
||||
|
||||
@mode = :replying
|
||||
@text_buffer = ''
|
||||
title = Corpus.find_topic_by_hash(parent.hash).truncated_message(Display::TITLE_WIDTH)
|
||||
Display.say
|
||||
Display.say "Writing a reply to topic '#{title}'"
|
||||
Display.say 'Type a period on a line by itself to end message.'
|
||||
|
||||
message_text = external_editor()
|
||||
|
||||
if message_text.length <= 1
|
||||
Display.say 'Empty message, discarding...'
|
||||
else
|
||||
Message.new(message_text, reply_topic).save!
|
||||
Display.say 'Reply saved!'
|
||||
end
|
||||
end
|
||||
|
||||
def edit(message_id = nil)
|
||||
|
@ -803,13 +753,18 @@ class Interface
|
|||
return
|
||||
end
|
||||
|
||||
@mode = :editing
|
||||
@old_message = message
|
||||
@text_buffer = ''
|
||||
title = message.truncated_message(Display::TITLE_WIDTH)
|
||||
Display.say
|
||||
Display.say "Editing message '#{title}'"
|
||||
Display.say 'Type a period on a line by itself to end message.'
|
||||
|
||||
message_text = external_editor(message.message)
|
||||
|
||||
if message_text.length <= 1
|
||||
Display.say 'Empty message, not updating...'
|
||||
else
|
||||
Message.edit(message_text, message)
|
||||
Display.say 'Message edited!'
|
||||
end
|
||||
end
|
||||
|
||||
def mark_read(message_id = nil)
|
||||
|
@ -864,71 +819,37 @@ class Interface
|
|||
end
|
||||
end
|
||||
|
||||
def replying_handler(line)
|
||||
line.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
|
||||
if line !~ /^\.$/
|
||||
if @text_buffer.empty?
|
||||
@text_buffer = line
|
||||
else
|
||||
@text_buffer = [@text_buffer, line].join("\n")
|
||||
end
|
||||
return
|
||||
def external_editor(preload_text = nil)
|
||||
tf = Tempfile.new('iris')
|
||||
|
||||
if preload_text
|
||||
tf.write(preload_text)
|
||||
tf.flush
|
||||
end
|
||||
|
||||
if @text_buffer.length <= 1
|
||||
Display.say 'Empty message, discarding...'
|
||||
else
|
||||
Message.new(@text_buffer, @reply_topic).save!
|
||||
Display.say 'Reply saved!'
|
||||
end
|
||||
@reply_topic = nil
|
||||
@mode = :browsing
|
||||
end
|
||||
raise "No `$EDITOR` environment variable set!" unless ENV['EDITOR'] && ENV['EDITOR'].length > 0
|
||||
|
||||
def editing_handler(line)
|
||||
if line !~ /^\.$/
|
||||
if @text_buffer.empty?
|
||||
@text_buffer = line
|
||||
else
|
||||
@text_buffer = [@text_buffer, line].join("\n")
|
||||
end
|
||||
return
|
||||
end
|
||||
system("#{ENV['EDITOR']} #{tf.path}")
|
||||
tf.rewind
|
||||
message_text = tf.read
|
||||
tf.unlink
|
||||
|
||||
if @text_buffer.length <= 1
|
||||
Display.say 'Empty message, not updating...'
|
||||
else
|
||||
Message.edit(@text_buffer, @old_message)
|
||||
Display.say 'Message edited!'
|
||||
end
|
||||
@reply_topic = nil
|
||||
@mode = :browsing
|
||||
end
|
||||
|
||||
def composing_handler(line)
|
||||
if line !~ /^\.$/
|
||||
if @text_buffer.empty?
|
||||
@text_buffer = line
|
||||
else
|
||||
@text_buffer = [@text_buffer, line].join("\n")
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if @text_buffer.length <= 1
|
||||
Display.say 'Empty message, discarding...'
|
||||
else
|
||||
Message.new(@text_buffer).save!
|
||||
Display.say 'Topic saved!'
|
||||
end
|
||||
@mode = :browsing
|
||||
message_text
|
||||
end
|
||||
|
||||
def handle(line)
|
||||
return browsing_handler(line) if @mode == :browsing
|
||||
return composing_handler(line) if @mode == :composing
|
||||
return replying_handler(line) if @mode == :replying
|
||||
return editing_handler(line) if @mode == :editing
|
||||
tokens = line.split(/\s/)
|
||||
cmd = tokens.first
|
||||
cmd = CMD_MAP[cmd] || cmd
|
||||
return self.send(cmd.to_sym) if ONE_SHOTS.include?(cmd) && tokens.length == 1
|
||||
return show_topic(cmd) if cmd =~ /^\d+$/
|
||||
# If we've gotten this far, we must have args. Let's handle 'em.
|
||||
arg = tokens.last
|
||||
return reply(arg) if cmd == 'reply'
|
||||
return edit(arg) if cmd == 'edit'
|
||||
return delete(arg) if cmd == 'delete'
|
||||
return mark_read(arg) if cmd == 'mark_read'
|
||||
Display.say 'Unrecognized command. Type "help" for a list of available commands.'
|
||||
end
|
||||
|
||||
def show_topic(num)
|
||||
|
@ -936,7 +857,6 @@ class Interface
|
|||
# TODO: Paginate here
|
||||
if index >= 0 && index < Corpus.topics.length
|
||||
msg = Corpus.topics[index]
|
||||
@reply_topic = msg.hash
|
||||
|
||||
Display.say msg.to_topic_display
|
||||
Display.say
|
||||
|
@ -956,15 +876,11 @@ class Interface
|
|||
end
|
||||
|
||||
def prompt
|
||||
return 'new~> ' if @mode == :composing
|
||||
return 'reply~> ' if @mode == :replying
|
||||
return 'edit~> ' if @mode == :editing
|
||||
"#{Config::AUTHOR}~> "
|
||||
end
|
||||
|
||||
def initialize(args)
|
||||
@history_loaded = false
|
||||
@mode = :browsing
|
||||
|
||||
Display.say "Welcome to Iris v#{Config::VERSION}. Type 'help' for a list of commands; Ctrl-D or 'quit' to leave."
|
||||
unread
|
||||
|
@ -1116,12 +1032,16 @@ class CLI
|
|||
end
|
||||
|
||||
class Startupper
|
||||
INTERACTIVE_OPTIONS = %w[-i --interactive]
|
||||
NONINTERACTIVE_OPTIONS = %w[-d --dump -h --help -v --version -s --stats --mark-all-read]
|
||||
NONFILE_OPTIONS = %w[-h --help -v --version]
|
||||
|
||||
def initialize(args)
|
||||
perform_file_checks unless Config::NONFILE_OPTIONS.include?(args)
|
||||
Startupper.perform_file_checks unless NONFILE_OPTIONS.include?(args)
|
||||
|
||||
load_corpus(args)
|
||||
|
||||
is_interactive = (args & Config::NONINTERACTIVE_OPTIONS).none? || (args & Config::INTERACTIVE_OPTIONS).any?
|
||||
is_interactive = (args & NONINTERACTIVE_OPTIONS).none? || (args & INTERACTIVE_OPTIONS).any?
|
||||
|
||||
Config.enable_debug_mode if (args & %w{--debug}).any?
|
||||
|
||||
|
@ -1132,8 +1052,7 @@ class Startupper
|
|||
end
|
||||
end
|
||||
|
||||
def perform_file_checks
|
||||
raise 'Should not try to perform file checks in test mode!' if $test_corpus_file
|
||||
def self.perform_file_checks
|
||||
unless File.exists?(Config::MESSAGE_FILE)
|
||||
Display.say "You don't have a message file at #{Config::MESSAGE_FILE}."
|
||||
response = Readline.readline 'Would you like me to create it for you? (y/n) ', true
|
||||
|
@ -1162,8 +1081,6 @@ class Startupper
|
|||
end
|
||||
|
||||
def load_corpus(args)
|
||||
$test_corpus_file = nil
|
||||
|
||||
if (args & %w{-f --test-file}).any?
|
||||
filename_idx = (args.index('-f') || args.index('--test-file')) + 1
|
||||
filename = args[filename_idx]
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
require 'minitest/autorun'
|
||||
require 'mocha/mini_test'
|
||||
require 'mocha/minitest'
|
||||
|
||||
# This allows the test to pretend that user "jerryberry" is logged in.
|
||||
ENV['USER'] = 'jerryberry'
|
||||
|
||||
# Set this before loading the code so that the Config constants load correctly.
|
||||
$test_corpus_file = "./tests/iris.messages.json"
|
||||
# Setting this before loading the main code file so that the Config contants
|
||||
# load correctly. This will allows the test to pretend that user "jerryberry"
|
||||
# is logged in.
|
||||
ENV.stubs(:[]).returns('jerryberry')
|
||||
|
||||
require './iris.rb'
|
||||
|
||||
describe Config do
|
||||
it 'has the Iris semantic version number' do
|
||||
Config::VERSION.must_match /^\d\.\d\.\d+$/
|
||||
_(Config::VERSION).must_match /^\d\.\d\.\d+$/
|
||||
end
|
||||
|
||||
it 'has the message file location' do
|
||||
Config::MESSAGE_FILE.must_match /\/\.iris\.messages$/
|
||||
_(Config::MESSAGE_FILE).must_match /\/\.iris\.messages$/
|
||||
end
|
||||
|
||||
it 'has the readline history file location' do
|
||||
Config::HISTORY_FILE.must_match /\/\.iris\.history$/
|
||||
_(Config::HISTORY_FILE).must_match /\/\.iris\.history$/
|
||||
end
|
||||
|
||||
it 'has the username' do
|
||||
Config::USER.must_equal 'jerryberry'
|
||||
_(Config::USER).must_equal 'jerryberry'
|
||||
end
|
||||
|
||||
it 'has a hostname' do
|
||||
Config::HOSTNAME.wont_be_nil
|
||||
_(Config::HOSTNAME).wont_be_nil
|
||||
end
|
||||
|
||||
it 'has the author' do
|
||||
Config::AUTHOR.must_equal "#{Config::USER}@#{Config::HOSTNAME}"
|
||||
_(Config::AUTHOR).must_equal "#{Config::USER}@#{Config::HOSTNAME}"
|
||||
end
|
||||
|
||||
describe '.find_files' do
|
||||
|
@ -43,17 +43,21 @@ describe Config do
|
|||
|
||||
it 'returns a list of Iris message files' do
|
||||
Config.stubs(:`).returns("foo\nbar\n")
|
||||
Config.find_files.must_equal ['foo', 'bar']
|
||||
_(Config.find_files).must_equal ['foo', 'bar']
|
||||
end
|
||||
|
||||
it 'returns an empty array if no Iris message files are found' do
|
||||
Config.stubs(:`).returns('')
|
||||
Config.find_files.must_equal []
|
||||
_(Config.find_files).must_equal []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Corpus do
|
||||
before do
|
||||
Corpus.load
|
||||
end
|
||||
|
||||
describe '.load' do
|
||||
it 'loads all the message files'
|
||||
it 'sets the corpus class variable'
|
||||
|
@ -62,10 +66,6 @@ describe Corpus do
|
|||
it 'creates parent-hash-to-child-indexes index'
|
||||
end
|
||||
|
||||
describe '.all' do
|
||||
it 'returns the entire corpus of messages'
|
||||
end
|
||||
|
||||
describe '.topics' do
|
||||
it 'returns all the messages which are topics'
|
||||
it 'does not return reply messages'
|
||||
|
@ -78,28 +78,26 @@ describe Corpus do
|
|||
|
||||
describe '.find_message_by_hash' do
|
||||
it 'returns nil if a nil is passed in' do
|
||||
Corpus.find_message_by_hash(nil).must_equal nil
|
||||
assert_nil Corpus.find_message_by_hash(nil)
|
||||
end
|
||||
|
||||
it 'returns nil if the hash is not found in the corpus' do
|
||||
# Corpus.load
|
||||
Corpus.find_message_by_hash('NoofMcGoof').must_equal nil
|
||||
assert_nil Corpus.find_message_by_hash('NoofMcGoof')
|
||||
end
|
||||
|
||||
it 'returns the message associated with the hash if it is found' do
|
||||
Corpus.load
|
||||
Corpus.find_message_by_hash("gpY2WW/jGcH+BODgySCwDANJlIM=").must_equal "Test"
|
||||
message = Corpus.find_message_by_hash("gpY2WW/jGcH+BODgySCwDANJlIM=\n")
|
||||
_(message.message).must_equal "Test"
|
||||
end
|
||||
end
|
||||
|
||||
describe '.find_all_by_parent_hash' do
|
||||
it 'returns an empty array if a nil is passed in' do
|
||||
Corpus.find_all_by_parent_hash(nil).must_equal []
|
||||
_(Corpus.find_all_by_parent_hash(nil)).must_equal []
|
||||
end
|
||||
|
||||
it 'returns an empty array if the hash is not a parent of any other messages' do
|
||||
skip
|
||||
Corpus.find_all_by_parent_hash('GoofMcDoof').must_equal []
|
||||
_(Corpus.find_all_by_parent_hash('GoofMcDoof')).must_equal []
|
||||
end
|
||||
|
||||
it 'returns an empty array if the hash is not found in the corpus'
|
||||
|
@ -108,7 +106,7 @@ describe Corpus do
|
|||
|
||||
describe '.find_topic_by_id' do
|
||||
it 'returns nil if a nil is passed in' do
|
||||
Corpus.find_topic_by_id(nil).must_equal nil
|
||||
assert_nil Corpus.find_topic_by_id(nil)
|
||||
end
|
||||
|
||||
describe 'when an index string is passed in' do
|
||||
|
@ -119,7 +117,7 @@ describe Corpus do
|
|||
|
||||
describe '.find_topic_by_hash' do
|
||||
it 'returns nil if a nil is passed in' do
|
||||
Corpus.find_topic_by_hash(nil).must_equal nil
|
||||
assert_nil Corpus.find_topic_by_hash(nil)
|
||||
end
|
||||
|
||||
describe 'when a hash string is passed in' do
|
||||
|
@ -135,10 +133,6 @@ describe IrisFile do
|
|||
end
|
||||
|
||||
describe Message do
|
||||
it 'has a file version' do
|
||||
Message::FILE_FORMAT.must_match /v\d/
|
||||
end
|
||||
|
||||
it 'exposes all its data attributes for reading'
|
||||
|
||||
it 'is #valid? if it has no errors'
|
||||
|
@ -163,33 +157,44 @@ end
|
|||
|
||||
describe Display do
|
||||
it 'has a setting for a minimum width of 80' do
|
||||
Display::MIN_WIDTH.must_equal 80
|
||||
_(Display::MIN_WIDTH).must_equal 80
|
||||
end
|
||||
|
||||
it 'has a setting for the calculated screen width'
|
||||
it 'has a setting for a minimum height of 8' do
|
||||
_(Display::MIN_HEIGHT).must_equal 8
|
||||
end
|
||||
|
||||
it 'has settings for the calculated screen geometry' do
|
||||
_(Display::WIDTH).wont_equal nil
|
||||
_(Display::HEIGHT).wont_equal nil
|
||||
end
|
||||
|
||||
describe '#topic_index_width' do
|
||||
it 'returns the a minimun length of 2' do
|
||||
Corpus.stubs(:topics).returns(%w{a})
|
||||
Display.topic_index_width.must_equal 2
|
||||
_(Display.topic_index_width).must_equal 2
|
||||
end
|
||||
|
||||
it 'returns the length in characters of the longest topic index' do
|
||||
Corpus.stubs(:topics).returns((0..1000).to_a)
|
||||
Display.topic_index_width.must_equal 4
|
||||
_(Display.topic_index_width).must_equal 4
|
||||
end
|
||||
|
||||
it 'returns 2 if there are no topics' do
|
||||
Corpus.stubs(:topics).returns([])
|
||||
Display.topic_index_width.must_equal 2
|
||||
_(Display.topic_index_width).must_equal 2
|
||||
end
|
||||
end
|
||||
|
||||
describe '#topic_author_width' do
|
||||
it 'returns the length in characters of the longest author\'s name'
|
||||
it 'returns the length in characters of the longest author\'s name' do
|
||||
Corpus.stubs(:authors).returns(['jerryberry@ctrl-c.club'])
|
||||
_(Display.topic_author_width).must_equal 22
|
||||
end
|
||||
|
||||
it 'returns 1 if there are no topics' do
|
||||
Corpus.stubs(:topics).returns([])
|
||||
Display.topic_author_width.must_equal 1
|
||||
Corpus.stubs(:authors).returns([])
|
||||
_(Display.topic_author_width).must_equal 1
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -254,46 +259,6 @@ describe Startupper do
|
|||
File.stubs(:stat).with(read_file_path).returns(data_file_stat)
|
||||
|
||||
Interface.stubs(:start)
|
||||
Display.stubs(:say)
|
||||
end
|
||||
|
||||
it 'offers to create a message file if the user doesn\'t have one' do
|
||||
File.stubs(:exists?).with(message_file_path).returns(false)
|
||||
Readline.expects(:readline).with('Would you like me to create it for you? (y/n) ', true).returns('y')
|
||||
IrisFile.expects(:create_message_file)
|
||||
|
||||
Startupper.new([])
|
||||
end
|
||||
|
||||
it 'creates a read file if the user doesn\'t have one' do
|
||||
File.stubs(:exists?).with(read_file_path).returns(false)
|
||||
IrisFile.expects(:create_read_file)
|
||||
|
||||
Startupper.new([])
|
||||
end
|
||||
|
||||
it 'warns the user if the message file permissions are wrong' do
|
||||
skip
|
||||
File.expects(:stat).with(message_file_path).returns(bad_file_stat)
|
||||
Display.expects(:say).with('Your message file has incorrect permissions! Should be "-rw-r--r--".')
|
||||
|
||||
Startupper.new([])
|
||||
end
|
||||
|
||||
it 'warns the user if the read file permissions are wrong' do
|
||||
skip
|
||||
File.stubs(:stat).with(read_file_path).returns(bad_file_stat)
|
||||
Display.expects(:say).with('Your read file has incorrect permissions! Should be "-rw-r--r--".')
|
||||
|
||||
Startupper.new([])
|
||||
end
|
||||
|
||||
it 'warns the user if the script file permissions are wrong' do
|
||||
skip
|
||||
File.expects(:stat).with(Config::IRIS_SCRIPT).returns(bad_file_stat)
|
||||
Display.expects(:say).with('The Iris file has incorrect permissions! Should be "-rwxr-xr-x".')
|
||||
|
||||
Startupper.new([])
|
||||
end
|
||||
|
||||
it 'starts the Interface if no command-line arguments are provided' do
|
||||
|
@ -315,11 +280,67 @@ describe Startupper do
|
|||
CLI.expects(:start).with(['-h'])
|
||||
Startupper.new(['-h'])
|
||||
end
|
||||
|
||||
it 'offers to create a message file if the user doesn\'t have one' do
|
||||
File.stubs(:exists?).with(message_file_path).returns(false)
|
||||
Display.stubs(:say)
|
||||
Readline.expects(:readline).with('Would you like me to create it for you? (y/n) ', true).returns('y')
|
||||
IrisFile.expects(:create_message_file)
|
||||
|
||||
Startupper.new([])
|
||||
end
|
||||
|
||||
it 'creates a read file if the user doesn\'t have one' do
|
||||
File.stubs(:exists?).with(read_file_path).returns(false)
|
||||
IrisFile.expects(:create_read_file)
|
||||
|
||||
Startupper.new([])
|
||||
end
|
||||
|
||||
it 'warns the user if the message file permissions are wrong' do
|
||||
File.expects(:stat).with(message_file_path).returns(bad_file_stat)
|
||||
Display.stubs(:say)
|
||||
message_lines = [
|
||||
"Your message file has incorrect permissions! Should be \"-rw-r--r--\".",
|
||||
"You can change this from the command line with:",
|
||||
" chmod 644 jerryberry/.iris.messages",
|
||||
"Leaving your file with incorrect permissions could allow unauthorized edits!"
|
||||
]
|
||||
Display.expects(:say).with(message_lines)
|
||||
|
||||
Startupper.new([])
|
||||
end
|
||||
|
||||
it 'warns the user if the read file permissions are wrong' do
|
||||
File.stubs(:stat).with(read_file_path).returns(bad_file_stat)
|
||||
Display.stubs(:say)
|
||||
message_lines = [
|
||||
"Your read file has incorrect permissions! Should be \"-rw-r--r--\".",
|
||||
"You can change this from the command line with:",
|
||||
" chmod 644 jerryberry/.iris.read"
|
||||
]
|
||||
Display.expects(:say).with(message_lines)
|
||||
|
||||
Startupper.new([])
|
||||
end
|
||||
|
||||
it 'warns the user if the script file permissions are wrong' do
|
||||
File.expects(:stat).with(Config::IRIS_SCRIPT).returns(bad_file_stat)
|
||||
Display.stubs(:say)
|
||||
message_lines = [
|
||||
"Your Iris file has incorrect permissions! Should be \"-rwxr-xr-x\".",
|
||||
"You can change this from the command line with:",
|
||||
" chmod 755 doots", "If this file has the wrong permissions the program may be tampered with!"
|
||||
]
|
||||
Display.expects(:say).with(message_lines)
|
||||
|
||||
Startupper.new([])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'String#colorize' do
|
||||
let(:color_string) {
|
||||
let(:color_strings) {
|
||||
"
|
||||
RED {r normal}\t{ri intense}\t{ru underline}\t{riu intense underline}
|
||||
{rv reverse}\t{riv intense}\t{ruv underline}\t{riuv intense underline}
|
||||
|
@ -335,28 +356,59 @@ describe 'String#colorize' do
|
|||
{cv reverse}\t{civ intense}\t{cuv underline}\t{ciuv intense underline}
|
||||
WHITE {w normal}\t{wi intense}\t{wu underline}\t{wiu intense underline}
|
||||
{wv reverse}\t{wiv intense}\t{wuv underline}\t{wiuv intense underline}
|
||||
"
|
||||
".split("\n")[1..-2]
|
||||
}
|
||||
|
||||
it 'produces the expected output' do
|
||||
skip
|
||||
# color_string.split("\n")[1].colorize.must_equal "\n RED \e[31mnormal\e[0m\t\e[1;31mintense\e[0m\t\e[31;4munderline\e[0m\t\e[1;31;4mintense underline\e[0m\n \e[31;7mreverse\e[0m\t\e[1;31;7mintense\e[0m\t\e[31;4;7munderline\e[0m\t\e[1;31;4;7mintense underline\e[0m\n GREEN \e[32mnormal\e[0m\t\e[1;32mintense\e[0m\t\e[32;4munderline\e[0m\t\e[1;32;4mintense underline\e[0m\n \e[32;7mreverse\e[0m\t\e[1;32;7mintense\e[0m\t\e[32;4;7munderline\e[0m\t\e[1;32;4;7mintense underline\e[0m\n YELLOW \e[33mnormal\e[0m\t\e[1;33mintense\e[0m\t\e[33;4munderline\e[0m\t\e[1;33;4mintense underline\e[0m\n \e[33;7mreverse\e[0m\t\e[1;33;7mintense\e[0m\t\e[33;4;7munderline\e[0m\t\e[1;33;4;7mintense underline\e[0m\n BLUE \e[34mnormal\e[0m\t\e[1;34mintense\e[0m\t\e[34;4munderline\e[0m\t\e[1;34;4mintense underline\e[0m\n \e[34;7mreverse\e[0m\t\e[1;34;7mintense\e[0m\t\e[34;4;7munderline\e[0m\t\e[1;34;4;7mintense underline\e[0m\n MAGENTA \e[35mnormal\e[0m\t\e[1;35mintense\e[0m\t\e[35;4munderline\e[0m\t\e[1;35;4mintense underline\e[0m\n \e[35;7mreverse\e[0m\t\e[1;35;7mintense\e[0m\t\e[35;4;7munderline\e[0m\t\e[1;35;4;7mintense underline\e[0m\n CYAN \e[36mnormal\e[0m\t\e[1;36mintense\e[0m\t\e[36;4munderline\e[0m\t\e[1;36;4mintense underline\e[0m\n \e[36;7mreverse\e[0m\t\e[1;36;7mintense\e[0m\t\e[36;4;7munderline\e[0m\t\e[1;36;4;7mintense underline\e[0m\n WHITE \e[37mnormal\e[0m\t\e[1;37mintense\e[0m\t\e[37;4munderline\e[0m\t\e[1;37;4mintense underline\e[0m\n \e[37;7mreverse\e[0m\t\e[1;37;7mintense\e[0m\t\e[37;4;7munderline\e[0m\t\e[1;37;4;7mintense underline\e[0m\n \e[0m"
|
||||
color_string.split("\n")[1].colorize.must_equal " RED \e[31mnormal\e[0m\t\e[1;31mintense\e[0m\t\e[31;4munderline\e[0m\t\e[1;31;4mintense underline\e[0m\e[0m"
|
||||
lead = "\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m "
|
||||
lines = [
|
||||
"RED \e[31mnormal\e[0m\t\e[1;31mintense\e[0m\t\e[31;4munderline\e[0m\t\e[1;31;4mintense underline\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m",
|
||||
" \e[31;7mreverse\e[0m\t\e[1;31;7mintense\e[0m\t\e[31;4;7munderline\e[0m\t\e[1;31;4;7mintense underline\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m",
|
||||
"GREEN \e[32mnormal\e[0m\t\e[1;32mintense\e[0m\t\e[32;4munderline\e[0m\t\e[1;32;4mintense underline\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m",
|
||||
" \e[32;7mreverse\e[0m\t\e[1;32;7mintense\e[0m\t\e[32;4;7munderline\e[0m\t\e[1;32;4;7mintense underline\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m",
|
||||
"YELLOW \e[33mnormal\e[0m\t\e[1;33mintense\e[0m\t\e[33;4munderline\e[0m\t\e[1;33;4mintense underline\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m",
|
||||
" \e[33;7mreverse\e[0m\t\e[1;33;7mintense\e[0m\t\e[33;4;7munderline\e[0m\t\e[1;33;4;7mintense underline\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m",
|
||||
"BLUE \e[34mnormal\e[0m\t\e[1;34mintense\e[0m\t\e[34;4munderline\e[0m\t\e[1;34;4mintense underline\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m",
|
||||
" \e[34;7mreverse\e[0m\t\e[1;34;7mintense\e[0m\t\e[34;4;7munderline\e[0m\t\e[1;34;4;7mintense underline\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m",
|
||||
"MAGENTA \e[35mnormal\e[0m\t\e[1;35mintense\e[0m\t\e[35;4munderline\e[0m\t\e[1;35;4mintense underline\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m",
|
||||
" \e[35;7mreverse\e[0m\t\e[1;35;7mintense\e[0m\t\e[35;4;7munderline\e[0m\t\e[1;35;4;7mintense underline\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m",
|
||||
"CYAN \e[36mnormal\e[0m\t\e[1;36mintense\e[0m\t\e[36;4munderline\e[0m\t\e[1;36;4mintense underline\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m",
|
||||
" \e[36;7mreverse\e[0m\t\e[1;36;7mintense\e[0m\t\e[36;4;7munderline\e[0m\t\e[1;36;4;7mintense underline\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m",
|
||||
"WHITE \e[37mnormal\e[0m\t\e[1;37mintense\e[0m\t\e[37;4munderline\e[0m\t\e[1;37;4mintense underline\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m",
|
||||
" \e[37;7mreverse\e[0m\t\e[1;37;7mintense\e[0m\t\e[37;4;7munderline\e[0m\t\e[1;37;4;7mintense underline\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m\e[0m",
|
||||
]
|
||||
|
||||
_(color_strings[0].colorize).must_equal lead + lines[0]
|
||||
_(color_strings[1].colorize).must_equal lead + lines[1]
|
||||
_(color_strings[2].colorize).must_equal lead + lines[2]
|
||||
_(color_strings[3].colorize).must_equal lead + lines[3]
|
||||
_(color_strings[4].colorize).must_equal lead + lines[4]
|
||||
_(color_strings[5].colorize).must_equal lead + lines[5]
|
||||
_(color_strings[6].colorize).must_equal lead + lines[6]
|
||||
_(color_strings[7].colorize).must_equal lead + lines[7]
|
||||
_(color_strings[8].colorize).must_equal lead + lines[8]
|
||||
_(color_strings[9].colorize).must_equal lead + lines[9]
|
||||
_(color_strings[10].colorize).must_equal lead + lines[10]
|
||||
_(color_strings[11].colorize).must_equal lead + lines[11]
|
||||
_(color_strings[12].colorize).must_equal lead + lines[12]
|
||||
_(color_strings[13].colorize).must_equal lead + lines[13]
|
||||
end
|
||||
|
||||
it 'returns an empty string wrapped with resets when provided an empty string' do
|
||||
''.colorize.must_equal "\e[0m\e[0m"
|
||||
_(''.colorize).must_equal "\e[0m\e[0m"
|
||||
end
|
||||
|
||||
it 'allows curly brackets to be escaped' do
|
||||
'I want \{no color\}'.colorize.must_equal "\e[0m\e[0mI want {no color}\e[0m\e[0m\e[0m"
|
||||
_('I want \{no color\}'.colorize).must_equal "\e[0m\e[0mI want {no color}\e[0m\e[0m\e[0m"
|
||||
end
|
||||
end
|
||||
|
||||
describe 'String#decolorize' do
|
||||
it 'returns the string with the coloring tags stripped'
|
||||
it 'returns the string with the coloring tags stripped' do
|
||||
_("{b colorful}".decolorize).must_equal "colorful"
|
||||
end
|
||||
|
||||
it 'allows curly brackets to be escaped' do
|
||||
'I want \{no color\}'.decolorize.must_equal "I want {no color}"
|
||||
_('I want \{no color\}'.decolorize).must_equal "I want {no color}"
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue