citeproc 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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