Class: CFF::File

Inherits:
Object
  • Object
show all
Defined in:
lib/cff/file.rb

Overview

File provides direct access to a CFF Index, with the addition of some filesystem utilities.

To be a fully compliant and valid CFF file its filename should be ‘CITATION.cff’. This class allows you to create files with any filename, and to validate the contents of those files independently of the preferred filename.

Constant Summary collapse

YAML_HEADER =

:nodoc:

"---\n"
CFF_COMMENT =
[
  "This CITATION.cff file was created by ruby-cff (v #{CFF::VERSION}).",
  'Gem: https://rubygems.org/gems/cff',
  'CFF: https://citation-file-format.github.io/'
].freeze
CFF_VALID_FILENAME =

:nodoc:

'CITATION.cff'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(filename, param, comment = CFF_COMMENT, create: false) ⇒ File

:call-seq:

new(filename, title) -> File
new(filename, index) -> File

Create a new File. Either a pre-existing Index can be passed in or, as with Index itself, a title can be supplied to initalize a new File.

All methods provided by Index are also available directly on File objects.



56
57
58
59
60
61
62
63
# File 'lib/cff/file.rb', line 56

def initialize(filename, param, comment = CFF_COMMENT, create: false)
  param = Index.new(param) unless param.is_a?(Index)

  @filename = filename
  @index = param
  @comment = comment
  @dirty = create
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args) ⇒ Object

:nodoc:



226
227
228
229
230
231
232
233
# File 'lib/cff/file.rb', line 226

def method_missing(name, *args) # :nodoc:
  if @index.respond_to?(name)
    @dirty = true if name.to_s.end_with?('=') # Remove to_s when Ruby >2.6.
    @index.send(name, *args)
  else
    super
  end
end

Instance Attribute Details

#commentObject

A comment to be inserted at the top of the resultant CFF file.



34
35
36
# File 'lib/cff/file.rb', line 34

def comment
  @comment
end

#filenameObject (readonly)

The filename of this CFF file.



37
38
39
# File 'lib/cff/file.rb', line 37

def filename
  @filename
end

Class Method Details

.format_comment(comment) ⇒ Object

:nodoc:



239
240
241
242
243
244
245
246
247
248
# File 'lib/cff/file.rb', line 239

def self.format_comment(comment) # :nodoc:
  return '' if comment.empty?

  comment = comment.scan(/.{1,75}/) if comment.is_a?(String)
  c = comment.map do |l|
    l.empty? ? '#' : "# #{l}"
  end.join("\n")

  "#{c}\n\n"
end

.open(file) ⇒ Object

:call-seq:

open(filename) -> File
open(filename) { |cff| block }

With no associated block, File.open is a synonym for ::read. If the optional code block is given, it will be passed the opened file as an argument and the File object will automatically be written (if edited) and closed when the block terminates.

File.open will create a new file if one does not already exist with the provided file name.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/cff/file.rb', line 87

def self.open(file)
  if ::File.exist?(file)
    content = ::File.read(file)
    comment = File.parse_comment(content)
    yaml = Util.parse_yaml(content)
  else
    comment = CFF_COMMENT
    yaml = ''
  end

  cff = new(file, yaml, comment, create: (yaml == ''))
  return cff unless block_given?

  begin
    yield cff
  ensure
    cff.write
  end
end

.parse_comment(content) ⇒ Object

:nodoc:



250
251
252
253
254
255
256
257
258
# File 'lib/cff/file.rb', line 250

def self.parse_comment(content) # :nodoc:
  content = content.split("\n")

  content.reduce([]) do |acc, line|
    break acc unless line.start_with?('#')

    acc << line.sub(/^#+/, '').strip
  end
end

.read(file) ⇒ Object

:call-seq:

read(filename) -> File

Read a file and parse it for subsequent manipulation.



69
70
71
72
73
74
# File 'lib/cff/file.rb', line 69

def self.read(file)
  content = ::File.read(file)
  comment = File.parse_comment(content)

  new(file, Util.parse_yaml(content), comment)
end

.validate(file, fail_on_filename: true) ⇒ Object

:call-seq:

validate(filename, fail_on_filename: true) -> Array

Read a file and return an array with the result. The result array is a three-element array, with true/false at index 0 to indicate pass/fail, an array of schema validation errors at index 1 (if any), and true/false at index 2 to indicate whether the filename passed/failed validation.

You can choose whether filename validation failure should cause overall validation failure with the fail_on_filename parameter (default: true).



118
119
120
# File 'lib/cff/file.rb', line 118

def self.validate(file, fail_on_filename: true)
  File.read(file).validate(fail_on_filename: fail_on_filename)
end

.validate!(file, fail_on_filename: true) ⇒ Object

:call-seq:

validate!(filename, fail_on_filename: true)

Read a file and raise a ValidationError upon failure. If an error is raised it will contain the detected validation failures for further inspection.

You can choose whether filename validation failure should cause overall validation failure with the fail_on_filename parameter (default: true).



131
132
133
# File 'lib/cff/file.rb', line 131

def self.validate!(file, fail_on_filename: true)
  File.read(file).validate!(fail_on_filename: fail_on_filename)
end

.write(file, cff, comment = '') ⇒ Object

:call-seq:

write(filename, File)
write(filename, Index)
write(filename, yaml)

Write the supplied File, Index or yaml string to file.



141
142
143
144
145
146
147
# File 'lib/cff/file.rb', line 141

def self.write(file, cff, comment = '')
  comment = cff.comment if cff.respond_to?(:comment)
  cff = cff.to_yaml unless cff.is_a?(String)
  content = File.format_comment(comment) + cff[YAML_HEADER.length...-1]

  ::File.write(file, content)
end

Instance Method Details

#respond_to_missing?(name, *all) ⇒ Boolean

:nodoc:

Returns:

  • (Boolean)


235
236
237
# File 'lib/cff/file.rb', line 235

def respond_to_missing?(name, *all) # :nodoc:
  @index.respond_to?(name, *all)
end

#to_yamlObject

:nodoc:



222
223
224
# File 'lib/cff/file.rb', line 222

def to_yaml # :nodoc:
  @index.to_yaml
end

#validate(fail_fast: false, fail_on_filename: true) ⇒ Object

:call-seq:

validate(fail_fast: false, fail_on_filename: true) -> Array

Validate this file and return an array with the result. The result array is a three-element array, with true/false at index 0 to indicate pass/fail, an array of schema validation errors at index 1 (if any), and true/false at index 2 to indicate whether the filename passed/failed validation.

You can choose whether filename validation failure should cause overall validation failure with the fail_on_filename parameter (default: true).



160
161
162
163
164
165
166
# File 'lib/cff/file.rb', line 160

def validate(fail_fast: false, fail_on_filename: true)
  valid_filename = (::File.basename(@filename) == CFF_VALID_FILENAME)
  result = (@index.validate(fail_fast: fail_fast) << valid_filename)
  result[0] &&= valid_filename if fail_on_filename

  result
end

#validate!(fail_fast: false, fail_on_filename: true) ⇒ Object

:call-seq:

validate!(fail_fast: false, fail_on_filename: true)

Validate this file and raise a ValidationError upon failure. If an error is raised it will contain the detected validation failures for further inspection.

You can choose whether filename validation failure should cause overall validation failure with the fail_on_filename parameter (default: true).

Raises:



177
178
179
180
181
182
183
184
# File 'lib/cff/file.rb', line 177

def validate!(fail_fast: false, fail_on_filename: true)
  result = validate(
    fail_fast: fail_fast, fail_on_filename: fail_on_filename
  )
  return if result[0]

  raise ValidationError.new(result[1], invalid_filename: !result[2])
end

#write(save_as: nil) ⇒ Object

:call-seq:

write(save_as: filename)

Write this CFF File. The save_as parameter can be used to save a new copy of this CFF File under a different filename, leaving the original file untouched. If save_as is used then the internal filename of the File will be updated to the supplied filename.



193
194
195
196
197
198
199
200
201
# File 'lib/cff/file.rb', line 193

def write(save_as: nil)
  unless save_as.nil?
    @filename = save_as
    @dirty = true
  end

  File.write(@filename, @index, @comment) if @dirty
  @dirty = false
end