citeproc 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,26 +4,52 @@ module CiteProc
4
4
 
5
5
  extend Forwardable
6
6
 
7
- @defaults ||= {
7
+ @defaults = {
8
8
  :locale => 'en-US',
9
9
  :style => 'chicago-author-date',
10
10
  :engine => 'citeproc-js',
11
11
  :format => 'html'
12
- }
12
+ }.freeze
13
13
 
14
14
  class << self
15
15
  attr_reader :defaults
16
16
  end
17
17
 
18
- attr_reader :options, :engine, :items
18
+ attr_reader :options, :engine, :items, :style
19
19
 
20
- def_delegators :@engine, :process, :append, :preview, :bibliography, :style, :style=, :abbreviate, :abbreviations, :abbreviations=
20
+ def_delegators :@engine, :abbreviate, :abbreviations, :abbreviations=
21
21
 
22
22
  def initialize(options = {})
23
23
  @options = Processor.defaults.merge(options)
24
24
  @engine = Engine.autodetect(@options).new(:processor => self)
25
+ @style = Style.load(@options[:style])
26
+ @locales = @options[:locale]
25
27
  @items = {}
26
28
  end
27
29
 
30
+ def style=(style)
31
+ @style = Style.load(style.to_s)
32
+ @engine.style = @style
33
+ @style
34
+ end
35
+
36
+ def locales=(locale)
37
+ @locales = { locale.to_sym => Locale.load(locale.to_s) }
38
+ @engine.locales = @locales
39
+ @locales
40
+ end
41
+
42
+ def process(*arguments)
43
+ @engine.process(CitationData(arguments.flatten(1)))
44
+ end
45
+
46
+ def append(*arguments)
47
+ @engine.append(CitationData(arguments.flatten(1)))
48
+ end
49
+
50
+ def bibliography(*arguments, &block)
51
+ @engine.bibliography(Selector.new(*arguments, &block))
52
+ end
53
+
28
54
  end
29
55
  end
@@ -0,0 +1,128 @@
1
+
2
+ module CiteProc
3
+
4
+ class Selector
5
+
6
+ @types = [:all, :any, :none].freeze
7
+ @matcher = Hash[*@types.zip([:all?, :any?, :none?]).flatten].freeze
8
+
9
+ @rb2cp = Hash[*(@types + [:skip]).zip(%w{
10
+ select include exclude quash
11
+ }).flatten]
12
+
13
+ @cp2rb = @rb2cp.invert.freeze
14
+ @rb2cp.freeze
15
+
16
+ class << self
17
+ attr_reader :types, :matcher, :rb2cp, :cp2rb
18
+ end
19
+
20
+ attr_reader :type, :conditions, :skip_conditions, :custom_matcher
21
+
22
+ def initialize(attributes = nil)
23
+ @conditions, @skip_conditions = {}, {}
24
+
25
+ if block_given?
26
+ @type = :ruby
27
+
28
+ else
29
+ unless attributes.nil? || attributes.empty?
30
+ attributes.symbolize_keys.each_pair do |key, conditions|
31
+ case key
32
+ when :all, :any, :none
33
+ @type = key
34
+ @conditions.merge!(conditions)
35
+
36
+ when :select, :include, :exclude
37
+ @type = Selector.cp2rb[key.to_s]
38
+ @conditions.merge!(conditions)
39
+
40
+ when :skip, :quash
41
+ @skip_conditions.merge!(conditions)
42
+
43
+ else
44
+ raise TypeError, "failed to create selector from #{key.inspect}"
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ def initialize_copy(other)
53
+ @type = other.type
54
+ @conditions = other.conditions.deep_copy
55
+ @skip_conditions = other.skip_conditions.deep_copy
56
+ @custom_matcher = other
57
+ end
58
+
59
+ def type=(type)
60
+ raise TypeError, "failed to set selector type to #{type.inspect}" unless
61
+ type.respond_to(:to_sym) && Selector.types.include?(type.to_sym)
62
+
63
+ @type = type.to_sym
64
+ end
65
+
66
+ def empty?
67
+ type.nil? && skip_conditions.empty?
68
+ end
69
+
70
+ def custom_matcher?
71
+ defined?(@custom_matcher)
72
+ end
73
+
74
+ alias ruby? custom_matcher?
75
+
76
+ def matches?(item)
77
+ if custom_matcher?
78
+ custom_matcher.call(item)
79
+ else
80
+ conditions.each_pair.send(matcher) do |field, value|
81
+ item[field].to_s == value.to_s
82
+ end
83
+ end
84
+ end
85
+
86
+ def skip?(item)
87
+ if custom_matcher?
88
+ false # skips are ignored for custom matchers
89
+ else
90
+ skip_conditions.each_pair.all? do |field, value|
91
+ item[field].to_s == value.to_s
92
+ end
93
+ end
94
+ end
95
+
96
+ def to_proc
97
+ Proc.new { |item| matches?(item) && !skip?(item) }
98
+ end
99
+
100
+ def to_citeproc
101
+ return nil if empty? || custom_matcher?
102
+
103
+ cp = {}
104
+ cp[Selector.rb2cp[type]] = conditions.map do |field, value|
105
+ { 'field' => field.to_s, 'value' => value.to_s }
106
+ end unless conditions.empty?
107
+
108
+ cp['quash'] = skip_conditions.map do |field, value|
109
+ { 'field' => field.to_s, 'value' => value.to_s }
110
+ end unless skip_conditions.empty?
111
+
112
+ cp
113
+ end
114
+
115
+ def to_json
116
+ MultiJson.encode(to_citeproc)
117
+ end
118
+
119
+ private
120
+
121
+ def matcher
122
+ Selector.matcher[type] || :all?
123
+ end
124
+
125
+
126
+ end
127
+
128
+ end
@@ -8,24 +8,33 @@ module CiteProc
8
8
  class Variable
9
9
 
10
10
  extend Forwardable
11
-
12
11
  include Comparable
13
12
 
14
13
  @fields = Hash.new { |h,k| h.fetch(k.to_sym, nil) }.merge({
15
- :date => %w{ accessed container event-date issued original-date },
16
-
14
+ :date => %w{
15
+ accessed container event-date issued original-date submitted
16
+ },
17
+
17
18
  :names => %w{
18
- author editor translator recipient interviewer publisher composer
19
- original-publisher original-author container-author collection-editor },
19
+ author collection-editor composer container-author recipient editor
20
+ editorial-director illustrator interviewer original-author translator
21
+ },
20
22
 
23
+ :number => %w{
24
+ chapter-number collection-number edition issue number number-of-pages
25
+ number-of-volumes volume
26
+ },
27
+
21
28
  :text => %w{
22
- id abstract annote archive archive-location archive-place authority
23
- call-number chapter-number citation-label citation-number collection-title
24
- container-title DOI edition event event-place first-reference-note-number
25
- genre ISBN issue jurisdiction keyword locator medium note number
26
- number-of-pages number-of-volumes original-publisher original-publisher-place
27
- original-title page page-first publisher publisher-place references
28
- section status title URL version volume year-suffix }
29
+ abstract annote archive archive_location archive-place authority
30
+ call-number citation-label citation-number collection-title
31
+ container-title container-title-short dimensions DOI event event-place
32
+ first-reference-note-number genre ISBN ISSN jurisdiction keyword
33
+ locator medium note original-publisher original-publisher-place
34
+ original-title page page-first PMID PMCID publisher publisher-place
35
+ references section source status title title-short URL version
36
+ year-suffix
37
+ }
29
38
  })
30
39
 
31
40
  @fields.each_value { |v| v.map!(&:to_sym) }
@@ -36,21 +45,43 @@ module CiteProc
36
45
 
37
46
  @fields[:name] = @fields[:names]
38
47
  @fields[:dates] = @fields[:date]
48
+ @fields[:numbers] = @fields[:number]
39
49
 
40
50
  @fields[:all] = @fields[:any] =
41
- [:date,:names,:text].reduce([]) { |s,a| s.concat(@fields[a]) }.sort
51
+ [:date,:names,:text,:number].reduce([]) { |s,a| s.concat(@fields[a]) }.sort
42
52
 
43
53
  @fields.freeze
44
54
 
55
+ @markup = /<[^>]*>/.freeze
56
+
57
+
45
58
  class << self
46
- attr_reader :fields, :types
59
+
60
+ attr_reader :fields, :types, :markup, :factories
47
61
 
48
- def parse(*args)
62
+ def create(value, type = nil)
63
+ create!(value, type)
64
+ rescue
65
+ nil
66
+ end
67
+
68
+ def create!(value, type = nil)
69
+ factory = factories[type]
70
+ value.is_a?(factory) ? value : factory.new(value)
49
71
  end
72
+
50
73
  end
51
74
 
52
- def initialize(attributes = nil)
53
- parse(attributes)
75
+ attr_accessor :value
76
+
77
+ def_delegators :@value, :to_s,
78
+ *::String.instance_methods(false).select {|m| m.to_s =~ /!$/ }
79
+
80
+ def_delegators :to_s, :=~, :===,
81
+ *::String.instance_methods(false).reject {|m| m.to_s =~ /^\W|!$|to_s/ }
82
+
83
+ def initialize(value = nil)
84
+ replace(value)
54
85
  yield self if block_given?
55
86
  end
56
87
 
@@ -58,35 +89,29 @@ module CiteProc
58
89
  @value = other.value.dup
59
90
  end
60
91
 
61
- attr_accessor :value
62
92
 
63
- def_delegators :@value, :to_s, :strip!, :upcase!, :downcase!, :sub!, :gsub!, :chop!, :chomp!, :rstrip!
64
-
65
- def_delegators :to_s, :empty?, :=~, :===, :match, :intern, :to_sym, :end_with?, :start_with?, :include?, :upcase, :downcase, :reverse, :chop, :chomp, :rstrip, :gsub, :sub, :size, :strip, :succ, :to_c, :to_r, :to_str, :split, :each_byte, :each_char, :each_line
93
+ # The replace method is typically called by the Variable's constructor. It
94
+ # will try to set the Variable to the passed in value and should accept
95
+ # a wide range of argument types; subclasses (especially Date and Names)
96
+ # override this method.
97
+ def replace(value)
98
+ raise TypeError, "failed to set value to #{value.inspect}" unless value.respond_to?(:to_s)
99
+ @value = value.to_s
100
+ self
101
+ end
66
102
 
67
- def parse(attributes)
68
- case
69
- when attributes.is_a?(Hash)
70
- attributes.each_key do |key|
71
- writer = "#{key}="
72
- send(writer, attributes[key]) if respond_to?(writer)
73
- end
74
- when attributes.respond_to?(:to_s)
75
- @value = attributes.to_s.dup
76
- end
77
- end
78
-
79
- def type
80
- @type ||= self.class.name.split(/::/)[-1].downcase.to_sym
81
- end
82
-
103
+ def type
104
+ @type ||= self.class.name.split(/::/)[-1].downcase.to_sym
105
+ end
106
+
107
+ # Returns true if the Variable can be (safely) cast to a numeric value.
83
108
  def numeric?
84
- self =~ /\d/ ? to_i : false
109
+ match(/\d/) ? to_i : false
85
110
  end
86
111
 
87
- # @returns (first) numeric data contained in the variable's value
112
+ # Returns (first) numeric data contained in the variable's value
88
113
  def to_i
89
- to_s =~ /(-?\d+)/ && $1.to_i || 0
114
+ to_s =~ /(-?\d+)/ && $1.to_i || 0
90
115
  end
91
116
 
92
117
  def to_f
@@ -94,28 +119,43 @@ module CiteProc
94
119
  end
95
120
 
96
121
  def strip_markup
97
- gsub(/<[^>]*>/, '')
122
+ gsub(Variable.markup, '')
98
123
  end
99
124
 
100
125
  def strip_markup!
101
- gsub!(/<[^>]*>/, '')
126
+ gsub!(Variable.markup, '')
102
127
  end
103
128
 
104
129
  def <=>(other)
105
130
  case
106
131
  when other.respond_to?(:strip_markup)
107
132
  strip_markup <=> other.strip_markup
133
+
108
134
  when other && other.respond_to?(:to_s)
109
135
  to_s <=> other.to_s
136
+
110
137
  else
111
138
  nil
112
139
  end
113
140
  end
114
141
 
142
+ alias to_citeproc to_s
143
+
115
144
  def to_json
116
- MultiJson.encode(@value)
145
+ MultiJson.encode(to_citeproc)
117
146
  end
147
+
148
+ def inspect
149
+ "#<#{self.class.name}: #{to_s.inspect}>"
150
+ end
151
+
118
152
  end
119
-
120
- class Text < Variable; end
153
+
154
+
155
+ class Text < Variable
156
+ end
157
+
158
+ class Number < Variable
159
+ end
160
+
121
161
  end
@@ -1,3 +1,3 @@
1
1
  module CiteProc
2
- VERSION = '0.0.2'.freeze
2
+ VERSION = '0.0.3'.freeze
3
3
  end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+ require 'tempfile'
3
+
4
+ module CiteProc
5
+
6
+ describe 'Assets' do
7
+ let(:file) { Tempfile.new('asset') }
8
+ let(:root) { File.dirname(file.path) }
9
+ let(:name) { File.basename(file.path) }
10
+ let(:extension) { File.extname(name) }
11
+
12
+ before(:all) do
13
+ file.write("asset content\n")
14
+ file.close
15
+ end
16
+
17
+ after(:all) { file.unlink }
18
+
19
+ describe 'Style' do
20
+
21
+ before(:all) do
22
+ @default_root = Style.root
23
+ @default_extension = Style.extension
24
+ Style.root = root
25
+ Style.extension = extension
26
+ end
27
+
28
+ after(:all) do
29
+ Style.root = @default_root
30
+ Style.extension = @default_extension
31
+ end
32
+
33
+ describe '.load' do
34
+
35
+ it 'accepts an absolute file name' do
36
+ Style.load(file.path).to_s.should == "asset content\n"
37
+ end
38
+
39
+ it 'accepts a file name' do
40
+ Style.load(name).to_s.should == "asset content\n"
41
+ end
42
+
43
+ it 'accepts a file name without extension' do
44
+ Style.load(name.sub(/#{extension}$/,'')).to_s.should == "asset content\n"
45
+ end
46
+
47
+
48
+ it 'accepts a uri' do
49
+ pending
50
+ end
51
+
52
+ it 'returns the given string if it is neither file nor uri' do
53
+ Style.load('foo bar!').to_s.should == 'foo bar!'
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+ end
60
+ end