alx 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +23 -0
- data/.simplecov +5 -0
- data/COPYING +20 -0
- data/Gemfile +4 -0
- data/LICENSE +674 -0
- data/README.md +5 -0
- data/Rakefile +16 -0
- data/alx.gemspec +24 -0
- data/bin/alx +14 -0
- data/features/add_books.feature +15 -0
- data/features/create_default_configuration.feature +8 -0
- data/features/give_meaningful_help.feature +8 -0
- data/features/remove_book.feature +17 -0
- data/features/step_definitions/alx_base_steps.rb +36 -0
- data/features/step_definitions/alx_configuration_steps.rb +19 -0
- data/features/step_definitions/alx_help_steps.rb +7 -0
- data/features/support/alx_extensions.rb +67 -0
- data/features/support/env.rb +27 -0
- data/lib/alx/add_handler.rb +25 -0
- data/lib/alx/book.rb +20 -0
- data/lib/alx/configuration.rb +51 -0
- data/lib/alx/controller.rb +43 -0
- data/lib/alx/editor.rb +33 -0
- data/lib/alx/executer.rb +13 -0
- data/lib/alx/handler.rb +62 -0
- data/lib/alx/help_handler.rb +39 -0
- data/lib/alx/library.rb +27 -0
- data/lib/alx/list_handler.rb +22 -0
- data/lib/alx/remove_handler.rb +36 -0
- data/lib/alx/renderer.rb +39 -0
- data/lib/alx/show_handler.rb +32 -0
- data/lib/alx/version.rb +3 -0
- data/lib/alx/viewer.rb +32 -0
- data/test/tc_book.rb +23 -0
- data/test/tc_controller.rb +37 -0
- data/test/tc_editor.rb +42 -0
- data/test/tc_library.rb +33 -0
- data/test/tc_list_handler.rb +48 -0
- data/test/tc_renderer.rb +41 -0
- data/test/tc_viewer.rb +58 -0
- data/test/test_helper.rb +69 -0
- metadata +168 -0
data/lib/alx/executer.rb
ADDED
data/lib/alx/handler.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'alx/library'
|
2
|
+
require 'optparse'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
module Alx
|
6
|
+
|
7
|
+
class Handler
|
8
|
+
attr_reader :command_name
|
9
|
+
|
10
|
+
def initialize( details )
|
11
|
+
@conf = details[ :configuration ]
|
12
|
+
@command_name = details[ :command ]
|
13
|
+
|
14
|
+
@opts = OptionParser.new
|
15
|
+
banner_text = @command_name
|
16
|
+
banner_text += ' [options]' if details[ :options ]
|
17
|
+
banner_text += " <#{details[ :rest_description ]}>" if details[ :rest_description ]
|
18
|
+
banner_text += "\n #{details[ :description ]}"
|
19
|
+
add_options( details[ :options ] ) if details[ :options ]
|
20
|
+
@opts.banner = banner_text
|
21
|
+
end
|
22
|
+
|
23
|
+
def help
|
24
|
+
@opts.help
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
def add_options( opts )
|
29
|
+
opts.each { | o | add_option( o ) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_option( opt )
|
33
|
+
case opt
|
34
|
+
when :editor
|
35
|
+
@opts.on( '-e EDITOR', '--editor EDITOR', 'Use EDITOR instead of predefined editor.' ) { | editor | @conf[ :editor ] = editor }
|
36
|
+
when :stdin
|
37
|
+
@opts.on( '-i', '--stdin', 'Use stdin instead of an editor.' ) { @conf.delete( :editor ) }
|
38
|
+
when :stdout
|
39
|
+
@opts.on( '-o', '--stdout', 'Print to stdout instead of starting a viewer.' ) { @conf[ :stdout ] = true }
|
40
|
+
when :html
|
41
|
+
@opts.on( '-h', '--html', 'Generate html from the original text file.' ) { @conf[ :html ] = true }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse_options
|
46
|
+
@conf[ :rest ] = @opts.parse( @conf[ :rest ] ).join( ' ' )
|
47
|
+
end
|
48
|
+
|
49
|
+
def options_error( message )
|
50
|
+
puts message
|
51
|
+
puts help
|
52
|
+
raise ""
|
53
|
+
end
|
54
|
+
|
55
|
+
def load_library
|
56
|
+
@lib = Alx::Library.new
|
57
|
+
@lib.load_library( @conf[ :default_shelf_dir ] )
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'alx/handler'
|
2
|
+
|
3
|
+
module Alx
|
4
|
+
|
5
|
+
class HelpHandler < Handler
|
6
|
+
def initialize( configuration, controller )
|
7
|
+
super(
|
8
|
+
:configuration => configuration,
|
9
|
+
:command => 'help',
|
10
|
+
:rest_description => 'command',
|
11
|
+
:description => "Gives more information on defined command." )
|
12
|
+
@controller = controller
|
13
|
+
end
|
14
|
+
|
15
|
+
def handle
|
16
|
+
parse_options
|
17
|
+
handler = @controller.handlers[ @conf[ :rest ] ]
|
18
|
+
if handler
|
19
|
+
puts handler.help
|
20
|
+
return;
|
21
|
+
end
|
22
|
+
|
23
|
+
default_help
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def default_help
|
28
|
+
executable_name = File.basename( $PROGRAM_NAME )
|
29
|
+
text = "Usage: #{executable_name} command [options]\n"
|
30
|
+
text += "An easy to use text document manager.\n\n"
|
31
|
+
text += "Commands:\n"
|
32
|
+
@controller.handlers.keys.each { | command | text+=" " + command + "\n" }
|
33
|
+
text += "\nFor more information type: #{executable_name} help <command>"
|
34
|
+
puts text
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
data/lib/alx/library.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'alx/book'
|
2
|
+
|
3
|
+
module Alx
|
4
|
+
|
5
|
+
class Library
|
6
|
+
def initialize
|
7
|
+
@books = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def load_library( directory )
|
11
|
+
Dir.open( directory ) do | dir |
|
12
|
+
files = dir.entries.keep_if { | i | i !~ /^\./ }
|
13
|
+
files.each do | file |
|
14
|
+
@books << Book.new( file.gsub( /_/, " " ), "#{directory}/#{file}" )
|
15
|
+
end
|
16
|
+
end
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def list( pattern )
|
21
|
+
@books.select { | book | book.title=~/#{pattern}/ }
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'alx/handler'
|
2
|
+
|
3
|
+
module Alx
|
4
|
+
|
5
|
+
class ListHandler < Handler
|
6
|
+
def initialize( configuration )
|
7
|
+
super(
|
8
|
+
:configuration => configuration,
|
9
|
+
:command => 'list',
|
10
|
+
:rest_description => 'pattern',
|
11
|
+
:description => "Lists matching books in the library. Lists all if there's no pattern given." )
|
12
|
+
end
|
13
|
+
|
14
|
+
def handle
|
15
|
+
parse_options
|
16
|
+
load_library.list( @conf[ :rest ] ).each { | book | puts book.title }
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'alx/handler'
|
2
|
+
|
3
|
+
module Alx
|
4
|
+
|
5
|
+
class RemoveHandler < Handler
|
6
|
+
def initialize( configuration )
|
7
|
+
super(
|
8
|
+
:configuration => configuration,
|
9
|
+
:command => 'remove',
|
10
|
+
:rest_description => 'pattern',
|
11
|
+
:description => "Removes matching books from the library." )
|
12
|
+
end
|
13
|
+
|
14
|
+
def handle
|
15
|
+
parse_options
|
16
|
+
options_error( "No pattern given. Please provide one." ) if @conf[ :rest ].empty?
|
17
|
+
matching_books = load_library.list( @conf[ :rest ] )
|
18
|
+
return remove_book( matching_books.pop ) if matching_books.size == 1
|
19
|
+
matching_books.each { | book | remove_book( book ) if ask?( "Do you really want to remove #{book.title}?" ) }
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def ask?( message )
|
24
|
+
printf message + " (yes/no) "
|
25
|
+
answer = $stdin.gets.chomp
|
26
|
+
return answer == "yes"
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove_book( book )
|
30
|
+
puts "Removing #{book.title}. A backup is saved to /tmp just in case."
|
31
|
+
FileUtils.mv( book.file_name, "/tmp" )
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
data/lib/alx/renderer.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'redcarpet'
|
2
|
+
|
3
|
+
module Alx
|
4
|
+
|
5
|
+
class Renderer
|
6
|
+
def initialize( conf )
|
7
|
+
@conf = conf
|
8
|
+
end
|
9
|
+
|
10
|
+
def render_to_file( book, destination = nil )
|
11
|
+
on_file( destination ) { | file | file.puts( render( book ) ) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def render( book )
|
15
|
+
if @conf[ :html ]
|
16
|
+
markdown = Redcarpet::Markdown.new( Redcarpet::Render::HTML.new, :fenced_code_blocks => true, :tables => true )
|
17
|
+
return markdown.render( book.content )
|
18
|
+
end
|
19
|
+
book.content
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def on_file( destination )
|
24
|
+
file_name = destination ? destination : temp_file
|
25
|
+
File.open( file_name, "w" ) do | file |
|
26
|
+
yield file
|
27
|
+
end
|
28
|
+
file_name
|
29
|
+
end
|
30
|
+
|
31
|
+
def temp_file
|
32
|
+
extension = @conf[ :html ] ? ".html" : ".md"
|
33
|
+
file_name = "/tmp/" + Time.now.to_i.to_s + extension
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'alx/handler'
|
2
|
+
require 'alx/renderer'
|
3
|
+
require 'alx/viewer'
|
4
|
+
|
5
|
+
module Alx
|
6
|
+
|
7
|
+
class ShowHandler < Handler
|
8
|
+
def initialize( configuration )
|
9
|
+
super(
|
10
|
+
:configuration => configuration,
|
11
|
+
:command => 'show',
|
12
|
+
:rest_description => 'pattern',
|
13
|
+
:description => "Shows book matching the given pattern.",
|
14
|
+
:options => [ :html, :stdout ] )
|
15
|
+
end
|
16
|
+
|
17
|
+
def handle
|
18
|
+
parse_options
|
19
|
+
options_error( "No pattern given. Please provide one." ) if @conf[ :rest ].empty?
|
20
|
+
load_library.list( @conf[ :rest ] ).each { | book | show_book( book ) }
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def show_book( book )
|
25
|
+
file_name = Renderer.new( @conf ).render_to_file( book )
|
26
|
+
Viewer.new( @conf ).view( file_name )
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
data/lib/alx/version.rb
ADDED
data/lib/alx/viewer.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module Alx
|
2
|
+
|
3
|
+
class Viewer
|
4
|
+
include Executer
|
5
|
+
|
6
|
+
def initialize( conf )
|
7
|
+
@conf = conf
|
8
|
+
end
|
9
|
+
|
10
|
+
def view( file_name )
|
11
|
+
if @conf[ :stdout ]
|
12
|
+
File.open( file_name ) do | file |
|
13
|
+
while line = file.gets
|
14
|
+
$stdout.puts line
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
return true
|
19
|
+
end
|
20
|
+
|
21
|
+
if @conf[ :html ]
|
22
|
+
start( @conf[ :html_viewer ] + " " + file_name )
|
23
|
+
return true
|
24
|
+
end
|
25
|
+
|
26
|
+
start( @conf[ :terminal_viewer ] + " " + file_name )
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
data/test/tc_book.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'alx/book'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
class BookTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@text = 'something something dark\nside\n'
|
10
|
+
File.stubs( :open ).yields( StringIO.new( @text ) )
|
11
|
+
@book = Alx::Book.new( 'title', 'filename' )
|
12
|
+
end
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
File.unstub( :open )
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_show_content
|
19
|
+
@book.content.should == @text
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'alx/controller'
|
4
|
+
require 'rspec'
|
5
|
+
|
6
|
+
class ControllerTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@testconf = {}
|
10
|
+
@controller = Alx::Controller.new( @testconf )
|
11
|
+
@handlers = @controller.handlers
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_constructor_creates_handlers
|
15
|
+
@controller.handlers.should have_key( 'add' )
|
16
|
+
@controller.handlers.should have_key( 'list' )
|
17
|
+
@controller.handlers.should have_key( 'show' )
|
18
|
+
@controller.handlers.should have_key( 'remove' )
|
19
|
+
@controller.handlers.should have_key( 'help' )
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_command_handler_should_be_called_if_exists
|
23
|
+
@testconf[ :command ] = 'help'
|
24
|
+
@handlers[ 'help' ] = HandlerStub.new( @testconf )
|
25
|
+
@controller.run()
|
26
|
+
@handlers[ 'help' ].handle_called.should == true
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_help_handler_should_be_called_if_command_does_not_exist
|
30
|
+
@testconf[ :command ] = 'somethingsomethingdarkside'
|
31
|
+
@handlers[ 'help' ] = HandlerStub.new( @testconf )
|
32
|
+
lambda { @controller.run() }.should raise_error
|
33
|
+
@handlers[ 'help' ].handle_called.should == true
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
data/test/tc_editor.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'alx/editor'
|
4
|
+
|
5
|
+
class EditorTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@conf = Hash.new
|
9
|
+
@editor = Alx::Editor.new( @conf )
|
10
|
+
@file_output = StringIO.new
|
11
|
+
File.stubs( :open ).yields( @file_output )
|
12
|
+
end
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
File.unstub( :open )
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_editor_error
|
19
|
+
@conf[ :editor ] = DUMMY_EXEC
|
20
|
+
lambda { @editor.edit( '/tmp/some_file' ) }.should raise_error
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_editor_returns_false
|
24
|
+
@conf[ :editor ] = FALSE_EXEC
|
25
|
+
lambda { @editor.edit( '/tmp/some_file' ) }.should raise_error
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_editor_success
|
29
|
+
@conf[ :editor ] = TRUE_EXEC
|
30
|
+
lambda { @editor.edit( '/tmp/some_file' ) }.should_not raise_error
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_stdin
|
34
|
+
text_to_write = "some string"
|
35
|
+
stub_in_and_out( text_to_write ) do
|
36
|
+
lambda { @editor.edit( '/tmp/some_file' ) }.should_not raise_error
|
37
|
+
end.should =~ /Reading.*from.*standard.*input/
|
38
|
+
@file_output.string.chomp.should == text_to_write
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
data/test/tc_library.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'alx/library'
|
4
|
+
require 'alx/book'
|
5
|
+
|
6
|
+
class LibraryTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup_dir( dir )
|
9
|
+
dir.entries << "first_file"
|
10
|
+
dir.entries << "almost_first_file"
|
11
|
+
dir.entries << "second_file"
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup
|
15
|
+
@directory = 'something'
|
16
|
+
@dir_stub = DirStub.new
|
17
|
+
setup_dir( @dir_stub )
|
18
|
+
Dir.stubs( :open ).yields( @dir_stub )
|
19
|
+
@library = Alx::Library.new.load_library( @directory )
|
20
|
+
end
|
21
|
+
|
22
|
+
def teardown
|
23
|
+
Dir.unstub( :open )
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_list_pattern
|
27
|
+
books = @library.list( 'first' )
|
28
|
+
books.size.should == 2
|
29
|
+
books[ 0 ].title.should == @dir_stub.entries[ 0 ].gsub( /_/, ' ' )
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|