Compare commits

...

8 Commits

Author SHA1 Message Date
Eric B. Budd d907c7a3b3 Bump Iris version to 1.1.0 2021-11-25 01:02:07 -06:00
Eric B. Budd b24bb192a9 Moved the Changelog out of the TODO file 2021-11-25 01:02:07 -06:00
Eric B. Budd 93b64d33a9 Expand and tidy tests
* Update expectation style to match new Mocha requirements
* Clean up broken Startupper tests
* Flesh out various test stubs
* Clean up formatting and other odds and ends
2021-11-25 01:02:07 -06:00
Eric B. Budd 1c124349eb Minor dead code removal and tidying 2021-11-25 01:02:07 -06:00
Eric B. Budd e9164717fd Clean up unused Config code 2021-11-25 01:02:07 -06:00
Eric B. Budd 20f97b28a1 Trim out unused Corpus code 2021-11-25 01:02:07 -06:00
Eric B. Budd 13546edb82 Remove automatic reply-to feature until we can make it work right 2021-11-25 01:02:07 -06:00
Eric B. Budd 79bc357828 Change message composition, reply, and editing to external editor 2021-11-25 01:02:07 -06:00
5 changed files with 323 additions and 343 deletions

69
CHANGELOG Normal file
View File

@ -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~

View File

@ -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
View File

@ -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
View File

@ -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]

View File

@ -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